using System.Text;
using Opc.Ua;
using Opc.Ua.Client;
using OpcUaManager.Models;

// Microsoft.AspNetCore.Http.ISession 과 Opc.Ua.Client.ISession 충돌 해소
using OpcUaISession = Opc.Ua.Client.ISession;

// Microsoft.AspNetCore.Http.StatusCodes 와 Opc.Ua.StatusCodes 충돌 해소
using OpcStatusCodes = Opc.Ua.StatusCodes;

namespace OpcUaManager.Services;

/// <summary>
/// OPC UA 세션을 싱글톤으로 관리합니다.
/// v1.5.374.70 API 기준으로 수정되었습니다.
/// </summary>
public class OpcSessionService : IAsyncDisposable
{
    private readonly ILogger<OpcSessionService> _logger;
    private readonly CertService _certService;

    private OpcUaISession?             _session;
    private ApplicationConfiguration?  _appConfig;
    private readonly SemaphoreSlim      _lock = new(1, 1);

    public bool   IsConnected => _session?.Connected == true;
    public string SessionId   => _session?.SessionId?.ToString() ?? string.Empty;

    public OpcSessionService(ILogger<OpcSessionService> logger, CertService certService)
    {
        _logger      = logger;
        _certService = certService;
    }

    public async Task<ConnectResult> ConnectAsync(ConnectRequest req)
    {
        await _lock.WaitAsync();
        try
        {
            if (IsConnected)
                await InternalCloseAsync();

            string endpointUrl = $"opc.tcp://{req.ServerIp}:{req.Port}";

            // ── 1. 클라이언트 인증서 로드 ────────────────────────────
            System.Security.Cryptography.X509Certificates.X509Certificate2? clientCert = null;
            if (!string.IsNullOrWhiteSpace(req.PfxPath) && File.Exists(req.PfxPath))
                clientCert = _certService.LoadPfx(req.PfxPath, req.PfxPassword);

            string appUri = string.IsNullOrWhiteSpace(req.ApplicationUri)
                ? $"urn:{System.Net.Dns.GetHostName()}:{req.ApplicationName}"
                : req.ApplicationUri;

            // ── 2. ApplicationConfiguration ───────────────────────────
            _appConfig = new ApplicationConfiguration
            {
                ApplicationName = req.ApplicationName,
                ApplicationType = ApplicationType.Client,
                ApplicationUri  = appUri,
                SecurityConfiguration = new SecurityConfiguration
                {
                    ApplicationCertificate = clientCert != null
                        ? new CertificateIdentifier { Certificate = clientCert }
                        : new CertificateIdentifier(),
                    TrustedPeerCertificates = new CertificateTrustList
                        { StoreType = "Directory", StorePath = Path.GetFullPath("pki/trusted") },
                    TrustedIssuerCertificates = new CertificateTrustList
                        { StoreType = "Directory", StorePath = Path.GetFullPath("pki/issuers") },
                    RejectedCertificateStore = new CertificateTrustList
                        { StoreType = "Directory", StorePath = Path.GetFullPath("pki/rejected") },
                    AutoAcceptUntrustedCertificates = true,
                    AddAppCertToTrustedStore        = true
                },
                TransportQuotas     = new TransportQuotas     { OperationTimeout     = 15000 },
                ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = req.SessionTimeoutMs }
            };

            // FIX CS1061: ValidateAsync → Validate (v1.5.374.70에서 동기 메서드)
            _appConfig.Validate(ApplicationType.Client);

            // FIX CS0104: StatusCodes → OpcStatusCodes 별칭 사용
            _appConfig.CertificateValidator.CertificateValidation += (_, e) =>
            {
                if (e.Error.StatusCode != OpcStatusCodes.Good) e.Accept = true;
            };

            // ── 3. Endpoint Discovery ─────────────────────────────────
            _logger.LogInformation("Endpoint Discovery: {Url}", endpointUrl);
            var endpointConfig = EndpointConfiguration.Create(_appConfig);

            // FIX CS0117: DiscoveryClient.CreateAsync 없음 → 동기 Create 사용
            EndpointDescriptionCollection endpoints;
            using (var discovery = DiscoveryClient.Create(new Uri(endpointUrl), endpointConfig))
            {
                endpoints = discovery.GetEndpoints(null);
            }

            var selected = endpoints
                .OrderByDescending(e => e.SecurityLevel)
                .FirstOrDefault(e => e.SecurityPolicyUri.Contains(req.SecurityPolicy))
                ?? endpoints.OrderByDescending(e => e.SecurityLevel).First();

            var endpoint = new ConfiguredEndpoint(null, selected, endpointConfig);

            // FIX CS1503: UserIdentity(user, byte[]) → UserIdentity(user, string)
            // v1.5.374.70 에서 password 파라미터가 string 으로 변경됨
            var identity = new UserIdentity(req.UserName, req.Password);

            // ── 5. Session.Create ─────────────────────────────────────
#pragma warning disable CS0618
            _session = await Session.Create(
                _appConfig,
                endpoint,
                false,
                "OpcUaManagerSession",
                (uint)req.SessionTimeoutMs,
                identity,
                null);
#pragma warning restore CS0618

            string secMode = selected.SecurityMode.ToString();
            _logger.LogInformation("OPC UA 연결 완료 | SessionId={Id} | Security={Sec}",
                _session.SessionId, secMode);

            return new ConnectResult
            {
                Success      = true,
                Message      = "연결 성공",
                SessionId    = _session.SessionId.ToString(),
                EndpointUrl  = endpointUrl,
                SecurityMode = secMode
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "OPC UA 연결 실패");
            return new ConnectResult { Success = false, Message = ex.Message };
        }
        finally
        {
            _lock.Release();
        }
    }

    public async Task<bool> DisconnectAsync()
    {
        await _lock.WaitAsync();
        try   { await InternalCloseAsync(); return true; }
        finally { _lock.Release(); }
    }

    private async Task InternalCloseAsync()
    {
        if (_session != null)
        {
            try   { await _session.CloseAsync(); } catch { /* ignore */ }
            _session.Dispose();
            _session = null;
        }
    }

    public async Task<(object? Value, string StatusCode)> ReadValueAsync(string nodeId)
    {
        if (!IsConnected || _session == null)
            throw new InvalidOperationException("세션이 연결되어 있지 않습니다.");

        var result = await _session.ReadValueAsync(nodeId);
        return (result.Value, result.StatusCode.ToString());
    }

    public Session? GetRawSession() => _session as Session;

    public async ValueTask DisposeAsync()
    {
        await InternalCloseAsync();
        _lock.Dispose();
        GC.SuppressFinalize(this);
    }
}
