using ExperionCrawler.Core.Application.Interfaces;
using ExperionCrawler.Core.Domain.Entities;
using ExperionCrawler.Infrastructure.Certificates;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Server;

namespace ExperionCrawler.Infrastructure.OpcUa;

// ── StandardServer 서브클래스 ─────────────────────────────────────────────────

/// <summary>커스텀 NodeManager를 주입한 StandardServer 파생 클래스.</summary>
internal sealed class ExperionStandardServer : StandardServer
{
    internal ExperionOpcServerNodeManager? NodeManager { get; private set; }

    protected override MasterNodeManager CreateMasterNodeManager(
        IServerInternal server, ApplicationConfiguration configuration)
    {
        NodeManager = new ExperionOpcServerNodeManager(server, configuration);
        return new MasterNodeManager(server, configuration, null, NodeManager);
    }

    protected override ServerProperties LoadServerProperties() => new()
    {
        ManufacturerName = "ExperionCrawler",
        ProductName      = "ExperionCrawler OPC UA Server",
        ProductUri       = "urn:ExperionCrawler:OpcUaServer",
        SoftwareVersion  = "1.0.0",
        BuildNumber      = "1",
        BuildDate        = DateTime.UtcNow
    };
}

// ── OPC UA 서버 서비스 ────────────────────────────────────────────────────────

/// <summary>
/// ExperionCrawler OPC UA 서버 서비스.
/// IHostedService 와 IExperionOpcServerService 를 모두 구현한다.
/// - IHostedService.StartAsync  : 자동 시작 플래그 파일이 있으면 서버 시작 (앱 재기동용)
/// - IHostedService.StopAsync   : 앱 종료 — 플래그 파일 유지 (재기동 시 자동 재시작)
/// - StartServerAsync           : UI 시작 버튼 — 서버 시작 + 플래그 파일 저장
/// - StopServerAsync            : UI 중지 버튼 — 서버 중지 + 플래그 파일 삭제
/// </summary>
public class ExperionOpcServerService : IExperionOpcServerService, IHostedService, IDisposable
{
    private readonly ILogger<ExperionOpcServerService> _logger;
    private readonly IServiceScopeFactory             _scopeFactory;
    private readonly IConfiguration                   _configuration;

    private ExperionStandardServer?        _server;
    private ExperionOpcServerNodeManager?  _nodeManager;

    private volatile bool  _running;
    private DateTime?      _startedAt;
    private string         _endpointUrl = string.Empty;

    private static readonly string FlagPath =
        Path.GetFullPath("opcserver_autostart.json");

    public ExperionOpcServerService(
        ILogger<ExperionOpcServerService> logger,
        IServiceScopeFactory scopeFactory,
        IConfiguration configuration)
    {
        _logger        = logger;
        _scopeFactory  = scopeFactory;
        _configuration = configuration;
    }

    // ── IHostedService ────────────────────────────────────────────────────────

    async Task IHostedService.StartAsync(CancellationToken ct)
    {
        if (!File.Exists(FlagPath)) return;
        try
        {
            _logger.LogInformation("[OpcServer] 자동 시작 플래그 감지 — OPC UA 서버 자동 시작");
            await StartInternalAsync(saveFlag: false, ct);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "[OpcServer] 자동 시작 실패 — 무시하고 계속");
        }
    }

    Task IHostedService.StopAsync(CancellationToken ct)
    {
        // 앱 종료 시: 서버 인스턴스 정리만, 플래그 파일은 유지 → 재기동 후 자동 시작
        StopInternal(deleteFlag: false);
        return Task.CompletedTask;
    }

    // ── IExperionOpcServerService ────────────────────────────────────────────

    public async Task StartServerAsync()
    {
        await StartInternalAsync(saveFlag: true, CancellationToken.None);
    }

    public Task StopServerAsync()
    {
        // UI 중지 버튼: 플래그 삭제 → 재기동 시 자동 시작 안 함
        StopInternal(deleteFlag: true);
        return Task.CompletedTask;
    }

    public OpcServerStatus GetStatus()
    {
        int clientCount = 0;
        try
        {
            clientCount = _server?.CurrentInstance?.SessionManager?.GetSessions()?.Count ?? 0;
        }
        catch { /* ignore */ }

        return new OpcServerStatus(
            _running, clientCount,
            _nodeManager?.TagNodeCount ?? 0,
            _endpointUrl, _startedAt);
    }

    public void UpdateNodeValue(string tagname, string? value, DateTime timestamp)
        => _nodeManager?.UpdateNodeValue(tagname, value, timestamp);

    public void RebuildAddressSpace(IEnumerable<RealtimePoint> points)
        => _nodeManager?.RebuildAddressSpace(points);

    // ── 내부 구현 ─────────────────────────────────────────────────────────────

    private async Task StartInternalAsync(bool saveFlag, CancellationToken ct)
    {
        if (_running)
        {
            _logger.LogWarning("[OpcServer] 이미 실행 중입니다.");
            return;
        }

        var config = BuildServerConfig();
        _server    = new ExperionStandardServer();
        await _server.StartAsync(config);
        _nodeManager = _server.NodeManager;

        _running     = true;
        _startedAt   = DateTime.UtcNow;
        var port     = _configuration.GetValue<int>("OpcUaServer:Port", 4841);
        _endpointUrl = $"opc.tcp://0.0.0.0:{port}";
        _logger.LogInformation("[OpcServer] 서버 시작: {Url}", _endpointUrl);

        // DB에서 realtime 포인트 조회 후 주소 공간 구성
        await RebuildFromDbAsync();

        if (saveFlag)
        {
            try { await File.WriteAllTextAsync(FlagPath, "{}", ct); }
            catch (Exception ex) { _logger.LogWarning(ex, "[OpcServer] 플래그 저장 실패 (무시)"); }
        }

        _nodeManager?.UpdateServerStatus("Running", _nodeManager.TagNodeCount);
    }

    private void StopInternal(bool deleteFlag)
    {
        if (!_running) return;
        try
        {
            _nodeManager?.UpdateServerStatus("Stopped", 0);
#pragma warning disable CS0618 // 'Stop()' is obsolete
            _server?.Stop();
#pragma warning restore CS0618 // 'Stop()' is obsolete
        }
        catch (Exception ex) { _logger.LogWarning(ex, "[OpcServer] 서버 Stop() 중 오류 (무시)"); }

        _server      = null;
        _nodeManager = null;
        _running     = false;
        _startedAt   = null;

        if (deleteFlag)
        {
            try { if (File.Exists(FlagPath)) File.Delete(FlagPath); }
            catch (Exception ex) { _logger.LogWarning(ex, "[OpcServer] 플래그 삭제 실패 (무시)"); }
            _logger.LogInformation("[OpcServer] 서버 중지 완료 (자동 재시작 플래그 삭제)");
        }
        else
        {
            _logger.LogInformation("[OpcServer] 서버 중지 완료 (앱 종료 — 자동 재시작 플래그 유지)");
        }
    }

    private async Task RebuildFromDbAsync()
    {
        if (_nodeManager == null) return;
        try
        {
            using var scope = _scopeFactory.CreateScope();
            var db     = scope.ServiceProvider.GetRequiredService<IExperionDbService>();
            var points = (await db.GetRealtimePointsAsync()).ToList();
            var dtMap  = await db.GetRealtimeNodeDataTypesAsync();
            _nodeManager.RebuildAddressSpace(points, dtMap);
            _logger.LogInformation("[OpcServer] 주소 공간 구성: {Count}개 태그 노드", points.Count);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "[OpcServer] 주소 공간 구성 실패 (무시)");
        }
    }

    private ApplicationConfiguration BuildServerConfig()
    {
        var port       = _configuration.GetValue<int>("OpcUaServer:Port", 4841);
        var enableSec  = _configuration.GetValue<bool>("OpcUaServer:EnableSecurity", false);
        var allowAnon  = _configuration.GetValue<bool>("OpcUaServer:AllowAnonymous", true);

        // 기존 클라이언트 인증서 재사용 (ExperionCertificateService 불변)
        var hostName = System.Net.Dns.GetHostName();
        var cert     = ExperionCertificateService.TryLoadCertificate(hostName);

        var userTokenPolicies = new UserTokenPolicyCollection();
        if (allowAnon)
            userTokenPolicies.Add(new UserTokenPolicy(UserTokenType.Anonymous) { PolicyId = "Anonymous" });

        var secPolicies = new ServerSecurityPolicyCollection
        {
            new() { SecurityMode = MessageSecurityMode.None, SecurityPolicyUri = SecurityPolicies.None }
        };
        if (enableSec)
        {
            secPolicies.Add(new() {
                SecurityMode      = MessageSecurityMode.SignAndEncrypt,
                SecurityPolicyUri = SecurityPolicies.Basic256Sha256
            });
            userTokenPolicies.Add(new UserTokenPolicy(UserTokenType.UserName)
                { PolicyId = "UserName", SecurityPolicyUri = SecurityPolicies.Basic256Sha256 });
        }

        return new ApplicationConfiguration
        {
            ApplicationName = "ExperionCrawlerServer",
            ApplicationType = ApplicationType.ClientAndServer,
            ApplicationUri  = $"urn:{hostName}:ExperionCrawlerServer",
            SecurityConfiguration = new SecurityConfiguration
            {
                ApplicationCertificate = cert != null
                    ? new CertificateIdentifier { Certificate = cert }
                    : 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 = 15_000 },
            ServerConfiguration = new ServerConfiguration
            {
                BaseAddresses        = new StringCollection { $"opc.tcp://0.0.0.0:{port}" },
                SecurityPolicies     = secPolicies,
                UserTokenPolicies    = userTokenPolicies,
                MaxSessionCount      = 100,
                MaxSubscriptionCount = 500,
                DiagnosticsEnabled   = false
            }
        };
    }

    public async ValueTask DisposeAsync()
    {
        if (_server != null)
        {
            try { await _server.StopAsync(CancellationToken.None).ConfigureAwait(false); } catch { /* ignore */ }
            _server = null;
        }
    }

    void IDisposable.Dispose()
    {
        try
        {
#pragma warning disable CS0618 // 'Stop()' is obsolete
            _server?.Stop();
#pragma warning restore CS0618 // 'Stop()' is obsolete
        }
        catch { /* ignore */ }
        _server = null;
    }
}
