using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.NetworkInformation;
using OpcPks.Core.Models;
using System.Diagnostics;
using System.Text;

namespace OpcPks.Core.Services
{
    public class CertificateGenerator
    {
        private readonly string _baseDataPath;
        private readonly OpcSessionManager _sessionManager;

        public CertificateGenerator(string baseDataPath, OpcSessionManager sessionManager)
        {
            _baseDataPath = baseDataPath;
            _sessionManager = sessionManager;
        }

        public async Task<bool> CreateHoneywellCertificateAsync(CertRequestModel model)
        {
            try
            {
                string appName = "OpcPksClient";
                // 초기 성공 모델과 동일한 URI 패턴 유지
                string applicationUri = $"urn:{Environment.MachineName}:OpcPksClient"; 
                
                string derPath = Path.Combine(_baseDataPath, "own", "certs", $"{appName}.der");
                string pfxPath = Path.Combine(_baseDataPath, "own", "private", $"{appName}.pfx");

                Directory.CreateDirectory(Path.GetDirectoryName(derPath)!);
                Directory.CreateDirectory(Path.GetDirectoryName(pfxPath)!);

                // 1. RSA 키 생성 (2048-bit)
                using var rsa = RSA.Create(2048);
                
                // 2. 인증서 요청 생성
                var request = new CertificateRequest(
                    $"CN={appName}", 
                    rsa, 
                    HashAlgorithmName.SHA256, 
                    RSASignaturePadding.Pkcs1);

                // 3. [Critical] 하니웰이 요구하는 '신분증 지문' 및 '도장' 추가
                
                // A. Subject Key Identifier (이게 없으면 하니웰 엔진이 인증서를 무시합니다)
                request.CertificateExtensions.Add(
                    new X509SubjectKeyIdentifierExtension(request.PublicKey, false));

                // B. Subject Alternative Name (SAN)
                var sanBuilder = new SubjectAlternativeNameBuilder();
                sanBuilder.AddUri(new Uri(applicationUri));
                sanBuilder.AddDnsName(Environment.MachineName);
                sanBuilder.AddDnsName("localhost");
                foreach (var ip in GetLocalIpAddresses()) sanBuilder.AddIpAddress(ip);
                request.CertificateExtensions.Add(sanBuilder.Build());

                // C. Key Usage (하니웰 보안 4종 세트)
                request.CertificateExtensions.Add(new X509KeyUsageExtension(
                    X509KeyUsageFlags.DigitalSignature | 
                    X509KeyUsageFlags.KeyEncipherment | 
                    X509KeyUsageFlags.NonRepudiation | 
                    X509KeyUsageFlags.DataEncipherment, 
                    true));

                // D. Enhanced Key Usage (EKU)
                request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(
                    new OidCollection { 
                        new Oid("1.3.6.1.5.5.7.3.1"), // Server Auth
                        new Oid("1.3.6.1.5.5.7.3.2")  // Client Auth
                    }, false));

                // E. Basic Constraints (End Entity 명시)
                request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));

                // 4. 인증서 생성 (초기 코드처럼 넉넉하게 10년)
                using var certificate = request.CreateSelfSigned(
                    DateTimeOffset.UtcNow.AddDays(-1), 
                    DateTimeOffset.UtcNow.AddYears(10));

                // 5. 파일 저장
                // PFX는 암호 없이 추출 (호환성)
                await File.WriteAllBytesAsync(pfxPath, certificate.Export(X509ContentType.Pfx));
                // DER은 하니웰 서버 전송용
                await File.WriteAllBytesAsync(derPath, certificate.Export(X509ContentType.Cert));

                Console.WriteLine($"✅ [Step 1] 하니웰 지문(SKI) 포함 인증서 제작 완료: {derPath}");

                // 6. 전송 및 검증
                string primaryIp = model.PrimaryIpA?.Trim() ?? "";
                if (string.IsNullOrEmpty(primaryIp)) throw new Exception("Primary IP 주소가 비어있습니다.");
                
                await TransferAndVerifyWithRetryAsync(model, primaryIp, $"{appName}.der", derPath);

                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 작업 최종 실패: {ex.Message}");
                throw;
            }
        }

        private async Task TransferAndVerifyWithRetryAsync(CertRequestModel model, string ip, string fileName, string localPath)
        {
            string winTrustedDir = @"ProgramData\Honeywell\Experion PKS\Server\data\CertStore\opcuaserver\pki\DefaultApplicationGroup\trusted\certs";
            string winRejectedDir = @"ProgramData\Honeywell\Experion PKS\Server\data\CertStore\opcuaserver\pki\DefaultApplicationGroup\rejected\certs";
            string auth = $"{model.AdminId}%{model.AdminPassword}";
            
            int maxRetries = 3;
            bool isAuthorized = false;

            for (int attempt = 1; attempt <= maxRetries; attempt++)
            {
                Console.WriteLine($"\n🔄 [{ip}] {attempt}회차 시퀀스 시작...");
                string trustedFile = $"{winTrustedDir}\\{fileName}";
                string rejectedFile = $"{winRejectedDir}\\{fileName}";

                // 1. 기존 파일 삭제 및 새 인증서 전송
                await DeleteFileFromHoneywell(ip, trustedFile, auth); 
                await PutFileToHoneywell(ip, localPath, trustedFile, auth);
                await Task.Delay(3000);

                // 2. 접속 시도 (하니웰 반응 유도)
                isAuthorized = await _sessionManager.SayHelloAsync(ip);
                if (isAuthorized) 
                {
                    Console.WriteLine($"🎯 [{ip}] 인증 성공!");
                    return;
                }

                Console.WriteLine($"⚠️ [{ip}] 거절됨(0x80830000). Rejected 폴더 확인 중...");
                await Task.Delay(2000); 

                // 3. Rejected 유배지로 갔다면 강제 이동 (하니웰의 '불신' 극복)
                if (await CheckFileExists(ip, rejectedFile, auth))
                {
                    Console.WriteLine($"🚩 [{ip}] Rejected 유배 확인! Trusted로 강제 이동합니다.");
                    string moveArgs = $"//{ip}/C$ -U \"{auth}\" -c \"rename \\\"{rejectedFile}\\\" \\\"{trustedFile}\\\"\"";
                    await ExecuteSmbCommandAsync(moveArgs);
                    
                    await Task.Delay(2000);
                    if (await _sessionManager.SayHelloAsync(ip)) return;
                }
            }

            if (!isAuthorized) throw new Exception($"❌ [{ip}] 지문 인식 실패 또는 Extensions 불일치. 수동 Trust 필요.");
        }

        private async Task PutFileToHoneywell(string ip, string localPath, string remotePath, string auth)
        {
            string args = $"//{ip}/C$ -U \"{auth}\" -c \"put \\\"{localPath}\\\" \\\"{remotePath}\\\"\"";
            await ExecuteSmbCommandAsync(args);
        }

        private async Task DeleteFileFromHoneywell(string ip, string remotePath, string auth)
        {
            try {
                string args = $"//{ip}/C$ -U \"{auth}\" -c \"del \\\"{remotePath}\\\"\"";
                await ExecuteSmbCommandAsync(args);
            } catch { }
        }

        private async Task<bool> CheckFileExists(string ip, string remotePath, string auth)
        {
            try {
                string args = $"//{ip}/C$ -U \"{auth}\" -c \"allinfo \\\"{remotePath}\\\"\"";
                string result = await ExecuteSmbCommandWithOutputAsync(args);
                return !result.Contains("NT_STATUS_OBJECT_NAME_NOT_FOUND");
            } catch { return false; }
        }

        private async Task ExecuteSmbCommandAsync(string args)
        {
            using var process = new Process {
                StartInfo = new ProcessStartInfo {
                    FileName = "smbclient",
                    Arguments = args,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };
            process.Start();
            string error = await process.StandardError.ReadToEndAsync();
            await process.WaitForExitAsync();
            if (process.ExitCode != 0 && !error.Contains("NT_STATUS_OBJECT_NAME_NOT_FOUND")) 
                throw new Exception($"SMB 실패: {error.Trim()}");
        }

        private async Task<string> ExecuteSmbCommandWithOutputAsync(string args)
        {
            using var process = new Process {
                StartInfo = new ProcessStartInfo {
                    FileName = "smbclient",
                    Arguments = args,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };
            process.Start();
            string output = await process.StandardOutput.ReadToEndAsync();
            string error = await process.StandardError.ReadToEndAsync();
            await process.WaitForExitAsync();
            return output + error;
        }

        private List<IPAddress> GetLocalIpAddresses()
        {
            return NetworkInterface.GetAllNetworkInterfaces()
                .Where(i => i.OperationalStatus == OperationalStatus.Up)
                .SelectMany(i => i.GetIPProperties().UnicastAddresses)
                .Where(a => a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                .Select(a => a.Address).ToList();
        }
    }
}