using System.Security.Cryptography.X509Certificates;
using ExperionCrawler.Core.Application.Interfaces;
using Microsoft.Extensions.Logging;
using Opc.Ua;

namespace ExperionCrawler.Infrastructure.Certificates;

/// <summary>
/// 기존 Program.cs 의 인증서 로직을 그대로 준수.
/// - pki/own/certs/{clientHostName}.pfx  로 저장
/// - 없으면 CertificateFactory 로 생성, 있으면 로드
/// - pki/trusted / pki/issuers / pki/rejected 폴더 보장
/// </summary>
public class ExperionCertificateService : IExperionCertificateService
{
    private const string PkiBase = "pki";
    private readonly ILogger<ExperionCertificateService> _logger;

    public ExperionCertificateService(ILogger<ExperionCertificateService> logger)
    {
        _logger = logger;
        EnsurePkiDirectories();
    }

    // ── 공개 메서드 ──────────────────────────────────────────────────────────

    public bool CertificateExists(string clientHostName)
        => File.Exists(PfxPath(clientHostName));

    public ExperionCertInfo GetCertificateInfo(string clientHostName)
    {
        var path = PfxPath(clientHostName);
        if (!File.Exists(path))
            return new ExperionCertInfo(false, null, null, null, path);

        try
        {
            using var cert = LoadPfx(path, "");
            return new ExperionCertInfo(true, cert.SubjectName.Name, cert.NotAfter, cert.Thumbprint, path);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "인증서 로드 실패: {Path}", path);
            return new ExperionCertInfo(false, null, null, null, path);
        }
    }

    public Task<ExperionCertResult> EnsureCertificateAsync(
        string              applicationUri,
        string              clientHostName,
        IEnumerable<string> subjectAltNames,
        string              pfxPassword = "")
    {
        return Task.Run(() => EnsureCertificateCore(applicationUri, clientHostName, subjectAltNames, pfxPassword));
    }

    private ExperionCertResult EnsureCertificateCore(
        string              applicationUri,
        string              clientHostName,
        IEnumerable<string> subjectAltNames,
        string              pfxPassword)
    {
        var path = PfxPath(clientHostName);

        // ── 기존 인증서 로드 (원본 Program.cs 동일 분기) ────────────────────
        if (File.Exists(path))
        {
            _logger.LogInformation("기존 인증서 사용: {Path}", path);
            using var existing = LoadPfx(path, pfxPassword);
            return new ExperionCertResult(true, $"기존 인증서 사용: {existing.Thumbprint}", existing.Thumbprint);
        }

        // ── 새 인증서 생성 (원본 Program.cs 동일 로직) ──────────────────────
        try
        {
            _logger.LogInformation("새 인증서 생성: {Uri}", applicationUri);

            var altNames = new List<string>(subjectAltNames) { "localhost", clientHostName };
            altNames = altNames.Distinct().ToList();

            // 원본: CertificateFactory.CreateCertificate(applicationUri, "OpcTestClient", $"CN=OpcTestClient, O=MyCompany", ...)
            var builder = CertificateFactory.CreateCertificate(
                applicationUri,
                "ExperionCrawlerClient",
                $"CN=ExperionCrawlerClient, O=ExperionCrawler",
                altNames
            );
            var cert = builder.CreateForRSA();

            File.WriteAllBytes(path, cert.Export(X509ContentType.Pfx, pfxPassword));
            // ── 8. Trusted 폴더에 DER(공개키) 복사 ───────────────────
            byte[] derBytes = cert.Export(X509ContentType.Cert);
            string derPath  = Path.Combine(PkiBase, "trusted", "certs",
                $"ExperionCrawlerClient [{cert.Thumbprint}].der");
            File.WriteAllBytes(derPath, derBytes);
            _logger.LogInformation("✨ 새 인증서 생성됨: {Uri}  Thumbprint={Tp}", applicationUri, cert.Thumbprint);

            return new ExperionCertResult(true, $"새 인증서 생성 완료: {cert.Thumbprint}", cert.Thumbprint);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "인증서 생성 실패");
            return new ExperionCertResult(false, $"인증서 생성 실패: {ex.Message}");
        }
    }

    // ── 내부 헬퍼 ────────────────────────────────────────────────────────────

    /// <summary>OpcUaClientService 에서 인증서를 로드할 때 사용</summary>
    public static X509Certificate2? TryLoadCertificate(string clientHostName, string pfxPassword = "")
    {
        var path = PfxPath(clientHostName);
        if (!File.Exists(path)) return null;
        return LoadPfx(path, pfxPassword);
    }

    private static X509Certificate2 LoadPfx(string path, string password)
        => new(path, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);

    private static string PfxPath(string clientHostName)
        => Path.Combine(PkiBase, "own", "certs", $"{clientHostName}.pfx");

    /// <summary>
    /// 원본 Program.cs 에서 필수라고 주석 달린 4개 폴더 생성
    /// pki/own/certs, pki/trusted/certs, pki/issuers/certs, pki/rejected/certs
    /// </summary>
    private static void EnsurePkiDirectories()
    {
        Directory.CreateDirectory($"{PkiBase}/own/certs");
        Directory.CreateDirectory($"{PkiBase}/trusted/certs");
        Directory.CreateDirectory($"{PkiBase}/issuers/certs");   
        Directory.CreateDirectory($"{PkiBase}/rejected/certs");
    }
}
