using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace AxCopilot.Services;

/// <summary>
/// AX Copilot 암호화 서비스.
///
/// 두 가지 암호화 계층을 제공합니다:
///
/// 1. **앱 공용 키 (Portable)** — 설정값(API 키 등) 암호화
///    - 앱 내장 고정 키 + AES-256-CBC
///    - 관리자 PC에서 암호화한 값이 모든 PC에서 동일하게 복호화됨
///    - AxKeyEncryptor 도구에서도 동일 키 사용
///
/// 2. **PC별 개인 키 (Local)** — 대화 내역 파일 암호화
///    - DPAPI로 보호되는 PC/사용자별 랜덤 마스터 키 + AES-256-GCM
///    - 해당 PC/사용자 계정에서만 복호화 가능
///    - 대화 파일(.axchat)은 다른 PC로 복사해도 열리지 않음
/// </summary>
public static class CryptoService
{
    // ═══════════════════════════════════════════════════════════════════════
    // 1. 앱 공용 키 — 설정값 암호화 (Portable, 모든 PC에서 동일)
    // ═══════════════════════════════════════════════════════════════════════

    // 앱 고유 시드. 이 값에서 PBKDF2로 256-bit 키를 파생합니다.
    // ※ 바이너리에 포함되므로 완벽한 보안은 아니지만, 평문 노출을 방지합니다.
    private static readonly byte[] AppSeed =
    {
        // "AX-Commander-Key-0104-sj.baeck" + 2-byte pad
        0x41, 0x58, 0x2D, 0x43, 0x6F, 0x6D, 0x6D, 0x61,
        0x6E, 0x64, 0x65, 0x72, 0x2D, 0x4B, 0x65, 0x79,
        0x2D, 0x30, 0x31, 0x30, 0x34, 0x2D, 0x73, 0x6A,
        0x2E, 0x62, 0x61, 0x65, 0x63, 0x6B, 0xA7, 0x5C
    };

    private static readonly byte[] AppSalt =
    {
        0x58, 0x43, 0x4D, 0x44, 0x53, 0x61, 0x6C, 0x74,  // "XCMDSalt"
        0x9E, 0x27, 0xC1, 0x4A, 0xB3, 0x06, 0x7F, 0xD8   // random
    };

    private static byte[]? _appKey;

    /// <summary>앱 공용 AES-256 키를 PBKDF2로 파생합니다.</summary>
    private static byte[] GetAppKey()
    {
        if (_appKey != null) return _appKey;
        using var pbkdf2 = new Rfc2898DeriveBytes(AppSeed, AppSalt, 100_000, HashAlgorithmName.SHA256);
        _appKey = pbkdf2.GetBytes(32); // 256-bit
        return _appKey;
    }

    /// <summary>
    /// 평문 문자열을 앱 공용 키로 AES-256-CBC 암호화하여 Base64 반환.
    /// 관리자 PC에서 암호화한 값이 모든 사용자 PC에서 동일하게 복호화됩니다.
    /// </summary>
    public static string PortableEncrypt(string plainText)
    {
        if (string.IsNullOrEmpty(plainText)) return "";
        var key = GetAppKey();
        using var aes = Aes.Create();
        aes.Key = key;
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;
        aes.GenerateIV();

        using var enc = aes.CreateEncryptor();
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var cipher = enc.TransformFinalBlock(plainBytes, 0, plainBytes.Length);

        // [IV(16)] [ciphertext]
        var result = new byte[16 + cipher.Length];
        Buffer.BlockCopy(aes.IV, 0, result, 0, 16);
        Buffer.BlockCopy(cipher, 0, result, 16, cipher.Length);
        return Convert.ToBase64String(result);
    }

    /// <summary>
    /// 앱 공용 키로 AES-256-CBC 복호화. PortableEncrypt의 역.
    /// </summary>
    public static string PortableDecrypt(string base64)
    {
        if (string.IsNullOrEmpty(base64)) return "";
        try
        {
            var raw = Convert.FromBase64String(base64);
            if (raw.Length < 17) return ""; // IV(16) + 최소 1블록

            var key = GetAppKey();
            var iv = new byte[16];
            Buffer.BlockCopy(raw, 0, iv, 0, 16);
            var cipher = new byte[raw.Length - 16];
            Buffer.BlockCopy(raw, 16, cipher, 0, cipher.Length);

            using var aes = Aes.Create();
            aes.Key = key;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using var dec = aes.CreateDecryptor();
            var plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
            return Encoding.UTF8.GetString(plain);
        }
        catch (Exception) { return ""; }
    }

    // ═══════════════════════════════════════════════════════════════════════
    // 1-B. 암호화 모드 분기 — encryptionEnabled에 따라 암호화 또는 패스스루
    // ═══════════════════════════════════════════════════════════════════════

    /// <summary>
    /// encryptionEnabled=true이면 PortableEncrypt, false이면 평문 그대로 반환.
    /// 운영 배포 시 true로 전환하면 자동으로 암호화가 적용됩니다.
    /// </summary>
    public static string EncryptIfEnabled(string plainText, bool encryptionEnabled)
        => encryptionEnabled ? PortableEncrypt(plainText) : plainText;

    /// <summary>
    /// encryptionEnabled=true이면 PortableDecrypt, false이면 평문 그대로 반환.
    /// 하위 호환: 암호화된 값이 들어와도 복호화 시도 후 실패하면 평문으로 간주.
    /// </summary>
    public static string DecryptIfEnabled(string value, bool encryptionEnabled)
    {
        if (string.IsNullOrEmpty(value)) return "";
        if (!encryptionEnabled) return value;
        // 암호화 모드: 복호화 시도, 실패하면 평문으로 간주 (마이그레이션 호환)
        var result = PortableDecrypt(value);
        return string.IsNullOrEmpty(result) ? value : result;
    }

    // ═══════════════════════════════════════════════════════════════════════
    // 2. PC별 개인 키 — 대화 내역 파일 암호화 (Local, PC 종속)
    // ═══════════════════════════════════════════════════════════════════════

    private static byte[] GetOrCreateMasterKey()
    {
        var dir  = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot");
        var path = Path.Combine(dir, ".master_key");

        if (File.Exists(path))
        {
            var dpapi = File.ReadAllBytes(path);
            return ProtectedData.Unprotect(dpapi, null, DataProtectionScope.CurrentUser);
        }

        // 최초 생성: 256-bit 랜덤 키 → DPAPI 보호 후 저장
        var key = RandomNumberGenerator.GetBytes(32);
        Directory.CreateDirectory(dir);
        File.WriteAllBytes(path, ProtectedData.Protect(key, null, DataProtectionScope.CurrentUser));
        return key;
    }

    /// <summary>바이트 배열을 PC별 마스터 키로 AES-256-GCM 암호화</summary>
    public static byte[] EncryptBytes(byte[] plainBytes)
    {
        var key   = GetOrCreateMasterKey();
        var nonce = RandomNumberGenerator.GetBytes(12);  // 96-bit nonce
        var tag   = new byte[16];                         // 128-bit tag
        var cipher = new byte[plainBytes.Length];

        using var aes = new AesGcm(key, 16);
        aes.Encrypt(nonce, plainBytes, cipher, tag);

        // [nonce(12)] [tag(16)] [ciphertext]
        var result = new byte[12 + 16 + cipher.Length];
        Buffer.BlockCopy(nonce, 0, result, 0, 12);
        Buffer.BlockCopy(tag, 0, result, 12, 16);
        Buffer.BlockCopy(cipher, 0, result, 28, cipher.Length);
        return result;
    }

    /// <summary>PC별 마스터 키로 AES-256-GCM 복호화</summary>
    public static byte[] DecryptBytes(byte[] encrypted)
    {
        if (encrypted.Length < 28) throw new CryptographicException("Invalid encrypted data");

        var key   = GetOrCreateMasterKey();
        var nonce  = new byte[12];
        var tag    = new byte[16];
        var cipher = new byte[encrypted.Length - 28];

        Buffer.BlockCopy(encrypted, 0, nonce, 0, 12);
        Buffer.BlockCopy(encrypted, 12, tag, 0, 16);
        Buffer.BlockCopy(encrypted, 28, cipher, 0, cipher.Length);

        var plain = new byte[cipher.Length];
        using var aes = new AesGcm(key, 16);
        aes.Decrypt(nonce, cipher, tag, plain);
        return plain;
    }

    /// <summary>문자열을 PC별 키로 AES-256-GCM 암호화하여 파일에 저장</summary>
    public static void EncryptToFile(string filePath, string content)
    {
        var plain = Encoding.UTF8.GetBytes(content);
        var enc   = EncryptBytes(plain);
        File.WriteAllBytes(filePath, enc);
    }

    /// <summary>PC별 키로 AES-256-GCM 암호화 파일을 복호화</summary>
    public static string DecryptFromFile(string filePath)
    {
        if (!File.Exists(filePath)) return "";
        var enc   = File.ReadAllBytes(filePath);
        var plain = DecryptBytes(enc);
        return Encoding.UTF8.GetString(plain);
    }
}
