using ExperionCrawler.Core.Application.DTOs;
using ExperionCrawler.Core.Application.Interfaces;
using ExperionCrawler.Core.Domain.Entities;
using ExperionCrawler.Infrastructure.Certificates;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Configuration;
using Opc.Ua.Client;
using System.Text;
using ISession = Opc.Ua.Client.ISession;

namespace ExperionCrawler.Infrastructure.OpcUa;

/// <summary>
/// Experion OPC UA 쓰기 서비스 — SP/OP/MD/MODE 쓰기 지원.
/// </summary>
public class ExperionOpcWriteClient : IExperionOpcWriteClient
{
    private readonly ILogger<ExperionOpcWriteClient> _logger;
    private readonly IOpcUaConfigProvider _configProvider;

    public ExperionOpcWriteClient(
        ILogger<ExperionOpcWriteClient> logger,
        IOpcUaConfigProvider configProvider)
    {
        _logger = logger;
        _configProvider = configProvider;
    }

    // ── SP/OP 쓰기 (WriteValue) ────────────────────────────────────────────────
    public async Task<ExperionWriteResultDto> WriteTagAsync(
        ExperionServerConfig cfg,
        string nodeId,
        double value,
        CancellationToken ct = default)
    {
        ISession? session = null;
        try
        {
            _logger.LogInformation("[ExperionWrite] WriteTag → {NodeId}, Value={Value}", nodeId, value);
            var appConfig = await _configProvider.GetConfigAsync(cfg);
            var endpoint = await SelectEndpointAsync(appConfig, cfg.EndpointUrl, ct);
            session = await CreateSessionAsync(appConfig, endpoint, cfg, "ExperionWriteSession");

            var writeValue = new WriteValue
            {
                NodeId = new NodeId(nodeId),
                AttributeId = Attributes.Value,
                Value = new DataValue(new Variant((double)value))
            };

            var writeResponse = await session.WriteAsync(
                null, new WriteValueCollection { writeValue }, ct);

            var status = new StatusCode((uint)writeResponse.Results[0]);
            bool success = StatusCode.IsGood(status);

            _logger.LogInformation("[ExperionWrite] WriteTag {NodeId} → Success={Success}, Status={Status}",
                nodeId, success, status);

            return new ExperionWriteResultDto
            {
                Success = success,
                NodeId = nodeId,
                Status = success ? "Good" : status.ToString(),
                Error = success ? null : $"OPC UA 상태 코드 오류: {status}"
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[ExperionWrite] WriteTag 실패 NodeId={NodeId}", nodeId);
            return new ExperionWriteResultDto
            {
                Success = false,
                NodeId = nodeId,
                Status = "Error",
                Error = ex.Message
            };
        }
        finally { await DisposeSessionAsync(session); }
    }

    // ── MD/MODE 쓰기 (EnumValueType) ───────────────────────────────────────────
    public async Task<ExperionModeResultDto> SetModeAsync(
        ExperionServerConfig cfg,
        string nodeId,
        string mode,
        CancellationToken ct = default)
    {
        ISession? session = null;
        try
        {
            _logger.LogInformation("[ExperionWrite] SetMode → {NodeId}, Mode={Mode}", nodeId, mode);
            var appConfig = await _configProvider.GetConfigAsync(cfg);
            var endpoint = await SelectEndpointAsync(appConfig, cfg.EndpointUrl, ct);
            session = await CreateSessionAsync(appConfig, endpoint, cfg, "ExperionModeSession");

            int enumValue = mode.ToUpperInvariant() switch
            {
                "MAN" => 0,
                "AUTO" => 1,
                _ => throw new ArgumentException($"지원하지 않는 모드: {mode}. MAN 또는 AUTO만 가능.")
            };

            var enumVal = new EnumValueType
            {
                Value = enumValue,
                DisplayName = mode.ToUpperInvariant(),
                Description = new LocalizedText("en", mode.ToUpperInvariant())
            };

            var writeValue = new WriteValue
            {
                NodeId = new NodeId(nodeId),
                AttributeId = Attributes.Value,
                Value = new DataValue(new Variant(enumVal))
            };

            var writeResponse = await session.WriteAsync(
                null, new WriteValueCollection { writeValue }, ct);

            var status = new StatusCode((uint)writeResponse.Results[0]);
            bool success = StatusCode.IsGood(status);

            _logger.LogInformation("[ExperionWrite] SetMode {NodeId} → Success={Success}, Status={Status}",
                nodeId, success, status);

            return new ExperionModeResultDto
            {
                Success = success,
                NodeId = nodeId,
                Mode = mode.ToUpperInvariant(),
                EnumValue = enumValue,
                Status = success ? "Good" : status.ToString(),
                Error = success ? null : $"OPC UA 상태 코드 오류: {status}"
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[ExperionWrite] SetMode 실패 NodeId={NodeId}", nodeId);
            return new ExperionModeResultDto
            {
                Success = false,
                NodeId = nodeId,
                Mode = mode.ToUpperInvariant(),
                EnumValue = -1,
                Status = "Error",
                Error = ex.Message
            };
        }
        finally { await DisposeSessionAsync(session); }
    }

    // ── 엔드포인트 선택 ────────────────────────────────────────────────────────
    private static async Task<ConfiguredEndpoint> SelectEndpointAsync(
        ApplicationConfiguration appConfig, string endpointUrl,
        CancellationToken ct = default)
    {
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        timeoutCts.CancelAfter(TimeSpan.FromSeconds(10));

        var endpointConfig = EndpointConfiguration.Create(appConfig);
        using var discovery = await DiscoveryClient.CreateAsync(
            appConfig, new Uri(endpointUrl), DiagnosticsMasks.All, timeoutCts.Token);

        var endpoints = await discovery.GetEndpointsAsync(null);
        var selected = endpoints
            .OrderByDescending(e => e.SecurityLevel)
            .FirstOrDefault(e => e.SecurityPolicyUri.Contains("Basic256Sha256"))
            ?? endpoints[0];

        return new ConfiguredEndpoint(null, selected, endpointConfig);
    }

    // ── 세션 생성 ──────────────────────────────────────────────────────────────
    private static async Task<ISession> CreateSessionAsync(
        ApplicationConfiguration appConfig,
        ConfiguredEndpoint endpoint,
        ExperionServerConfig cfg,
        string sessionName)
    {
        var identity = new UserIdentity(cfg.UserName, Encoding.UTF8.GetBytes(cfg.Password));
#pragma warning disable CS8625
        return await new DefaultSessionFactory(null).CreateAsync(
            appConfig, endpoint, false, sessionName, 60_000, identity, null, CancellationToken.None);
#pragma warning restore CS8625
    }

    // ── 세션 정리 ──────────────────────────────────────────────────────────────
    private static async Task DisposeSessionAsync(ISession? session)
    {
        if (session == null) return;
        try { await session.CloseAsync(); } catch { }
        finally { try { session.Dispose(); } catch { } }
    }
}
