using Grpc.Net.Client;
using Hc900.Gateway;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;

namespace Hc900Crawler.Infrastructure.Hc900;

/// <summary>공유 설정 — 모든 컨트롤러가 공통으로 사용</summary>
public class GatewaySharedConfig
{
    public string BinaryPath    { get; set; } = "";
    public string LdLibraryPath { get; set; } = "";
    public string LogDir        { get; set; } = "/tmp";
}

/// <summary>컨트롤러 1대 설정</summary>
public class ControllerConfig
{
    public string Id              { get; set; } = "";
    public string Name            { get; set; } = "";
    public string ControllerIp    { get; set; } = "";
    public int    ControllerPort  { get; set; } = 502;
    public int    GrpcPort        { get; set; } = 50051;
    public int    PollIntervalMs  { get; set; } = 1000;
    public string RegisterMapPath { get; set; } = "";
    public bool   Enabled         { get; set; } = true;
}

/// <summary>전체 Config 루트</summary>
public class MultiControllerConfig
{
    public GatewaySharedConfig    Shared      { get; set; } = new();
    public List<ControllerConfig> Controllers { get; set; } = new();
}

public class ControllerStatus
{
    public string      ControllerId { get; set; } = "";
    public string      Name         { get; set; } = "";
    public bool        Running      { get; set; }
    public int?        Pid          { get; set; }
    public string      Message      { get; set; } = "";
    public string      ControllerIp { get; set; } = "";
    public int         GrpcPort     { get; set; }
    public DateTime?   StartedAt    { get; set; }
    public List<string> RecentLog   { get; set; } = new();
}

public class ControllerProcessManager : BackgroundService
{
    private readonly ILogger<ControllerProcessManager> _logger;

    // D1: ConcurrentDictionary — HTTP 스레드 + 감시 루프 동시 접근 안전
    private readonly ConcurrentDictionary<string, Process?> _processes = new();
    private readonly ConcurrentDictionary<string, DateTime?> _startedAt = new();
    // D1: StartAsync TOCTOU 방지 — 컨트롤러별 1개 세마포어
    private readonly ConcurrentDictionary<string, SemaphoreSlim> _startLocks = new();

    private MultiControllerConfig _config = new();
    private readonly object _configLock = new();
    private static readonly string ConfigPath = FindConfigPath();

    private static string FindConfigPath()
    {
        var dir = new DirectoryInfo(AppContext.BaseDirectory);
        for (int i = 0; i < 8 && dir != null; i++, dir = dir.Parent)
        {
            var c = Path.Combine(dir.FullName, "config", "gateway-config.json");
            if (File.Exists(c)) return c;
        }
        return Path.Combine(AppContext.BaseDirectory,
            "..", "..", "..", "..", "..", "..", "config", "gateway-config.json");
    }

    private static readonly JsonSerializerOptions _jsonOpts = new()
    {
        PropertyNameCaseInsensitive = true,
        WriteIndented = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    };

    public ControllerProcessManager(ILogger<ControllerProcessManager> logger)
    {
        _logger = logger;
        ReloadConfig();
    }

    // D1: _config 읽기는 lock 안에서
    public MultiControllerConfig Config
    {
        get { lock (_configLock) return _config; }
    }

    public void ReloadConfig()
    {
        try
        {
            var path = Path.GetFullPath(ConfigPath);
            if (!File.Exists(path)) { _logger.LogWarning("[ProcMgr] config 없음: {P}", path); return; }
            var json = File.ReadAllText(path);
            var loaded = JsonSerializer.Deserialize<MultiControllerConfig>(json, _jsonOpts)
                         ?? new MultiControllerConfig();
            lock (_configLock) { _config = loaded; }
            _logger.LogInformation("[ProcMgr] 컨트롤러 {Count}대 로드", loaded.Controllers.Count);
        }
        catch (Exception ex) { _logger.LogWarning(ex, "[ProcMgr] config 로드 실패"); }
    }

    public static void SaveConfig(MultiControllerConfig cfg)
    {
        var path = Path.GetFullPath(ConfigPath);
        Directory.CreateDirectory(Path.GetDirectoryName(path)!);
        File.WriteAllText(path, JsonSerializer.Serialize(cfg, _jsonOpts));
    }

    public List<ControllerStatus> GetAllStatus()
    {
        List<ControllerConfig> ctrls;
        lock (_configLock) { ctrls = _config.Controllers.ToList(); }
        return ctrls.Select(c => GetStatus(c.Id)).ToList();
    }

    public ControllerStatus GetStatus(string controllerId)
    {
        ControllerConfig? cfg;
        MultiControllerConfig snapshot;
        lock (_configLock) { snapshot = _config; cfg = snapshot.Controllers.FirstOrDefault(c => c.Id == controllerId); }
        if (cfg == null) return new ControllerStatus { ControllerId = controllerId, Message = "설정 없음" };

        _processes.TryGetValue(controllerId, out var p);
        var running = p != null && !p.HasExited;

        var logPath = Path.Combine(snapshot.Shared.LogDir, $"hc900_gateway_{controllerId}.log");
        var log = new List<string>();
        try { if (File.Exists(logPath)) log = File.ReadLines(logPath).TakeLast(20).ToList(); } catch { }

        _startedAt.TryGetValue(controllerId, out var startedAt);
        return new ControllerStatus
        {
            ControllerId = controllerId,
            Name         = cfg.Name,
            Running      = running,
            Pid          = running ? p!.Id : null,
            Message      = running ? $"실행 중 (PID {p!.Id})" : "중지됨",
            ControllerIp = cfg.ControllerIp,
            GrpcPort     = cfg.GrpcPort,
            StartedAt    = startedAt,
            RecentLog    = log,
        };
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("[ProcMgr] 시작");

        List<ControllerConfig> initial;
        lock (_configLock) { initial = _config.Controllers.Where(c => c.Enabled).ToList(); }

        await Task.Delay(2000, stoppingToken);
        foreach (var ctrl in initial)
        {
            var (ok, msg) = await StartAsync(ctrl.Id);
            _logger.LogInformation("[ProcMgr] {Id} 초기 시작: {Msg}", ctrl.Id, msg);
        }

        while (!stoppingToken.IsCancellationRequested)
        {
            try { await Task.Delay(5000, stoppingToken); } catch (OperationCanceledException) { break; }

            List<ControllerConfig> enabledCtrls;
            lock (_configLock) { enabledCtrls = _config.Controllers.Where(c => c.Enabled).ToList(); }
            foreach (var ctrl in enabledCtrls)
            {
                if (_processes.TryGetValue(ctrl.Id, out var p) && p != null && p.HasExited)
                {
                    _logger.LogWarning("[ProcMgr] {Id} 종료됨 (exit={Code}) — 재시작", ctrl.Id, p.ExitCode);
                    _processes.TryRemove(ctrl.Id, out _);
                    _startedAt.TryRemove(ctrl.Id, out _);
                    try { await Task.Delay(5000, stoppingToken); } catch (OperationCanceledException) { break; }
                    var (ok, msg) = await StartAsync(ctrl.Id);
                    _logger.LogInformation("[ProcMgr] {Id} 재시작: {Msg}", ctrl.Id, msg);
                }
            }
        }
    }

    public async Task<(bool Ok, string Msg)> StartAsync(string controllerId)
    {
        // D1: 컨트롤러별 세마포어로 동시 중복 시작 방지 (TOCTOU)
        var sem = _startLocks.GetOrAdd(controllerId, _ => new SemaphoreSlim(1, 1));
        if (!await sem.WaitAsync(0))
            return (false, "이미 시작 처리 중");
        try
        {
            MultiControllerConfig snapshot;
            lock (_configLock) { snapshot = _config; }
            var cfg = snapshot.Controllers.FirstOrDefault(c => c.Id == controllerId);
            if (cfg == null) return (false, $"컨트롤러 {controllerId} 설정 없음");

            if (_processes.TryGetValue(controllerId, out var existing) && existing != null && !existing.HasExited)
                return (false, "이미 실행 중");

            if (!File.Exists(snapshot.Shared.BinaryPath))
                return (false, $"바이너리 없음: {snapshot.Shared.BinaryPath}");
            if (string.IsNullOrEmpty(cfg.ControllerIp))
                return (false, "Controller IP 미설정");

            var logPath = Path.Combine(snapshot.Shared.LogDir, $"hc900_gateway_{controllerId}.log");
            try { File.WriteAllText(logPath, ""); } catch { }

            // D7: 인수 순서 — 기존 argv[1-3] 호환 유지, 뒤에 추가
            // argv[1]=host  argv[2]=map_path  argv[3]=poll_ms  argv[4]=grpc_port  argv[5]=modbus_port
            var psi = new ProcessStartInfo
            {
                FileName  = snapshot.Shared.BinaryPath,
                Arguments = $"{cfg.ControllerIp} {cfg.RegisterMapPath} {cfg.PollIntervalMs} {cfg.GrpcPort} {cfg.ControllerPort}",
                RedirectStandardOutput = true,
                RedirectStandardError  = true,
                UseShellExecute = false,
                CreateNoWindow  = true,
            };
            if (!string.IsNullOrEmpty(snapshot.Shared.LdLibraryPath))
                psi.EnvironmentVariables["LD_LIBRARY_PATH"] = snapshot.Shared.LdLibraryPath;

            var proc = new Process { StartInfo = psi };
            proc.OutputDataReceived += (_, e) => { if (e.Data != null) AppendLog(logPath, e.Data); };
            proc.ErrorDataReceived  += (_, e) => { if (e.Data != null) AppendLog(logPath, e.Data); };
            proc.Start();
            proc.BeginOutputReadLine();
            proc.BeginErrorReadLine();

            _processes[controllerId] = proc;
            _startedAt[controllerId] = DateTime.UtcNow;

            // D6: 고정 2초 대기 → gRPC 헬스체크 폴링 (최대 10초)
            await Task.Delay(500);
            for (int i = 0; i < 10; i++)
            {
                if (proc.HasExited) return (false, "즉시 종료됨 — 로그 확인");
                try
                {
                    using var tempChannel = GrpcChannel.ForAddress($"http://localhost:{cfg.GrpcPort}");
                    var tempClient = new ModbusGateway.ModbusGatewayClient(tempChannel);
                    var health = await tempClient.HealthCheckAsync(
                        new HealthCheckRequest(), deadline: DateTime.UtcNow.AddSeconds(1));
                    if (health.Status == HealthCheckResponse.Types.ServingStatus.Serving)
                        return (true, $"시작 PID={proc.Id} (헬스체크 통과, {i + 1}회)");
                }
                catch { }
                await Task.Delay(1000);
            }
            return proc.HasExited
                ? (false, "즉시 종료됨 — 로그 확인")
                : (true, $"시작 PID={proc.Id} (헬스체크 미응답)");
        }
        finally { sem.Release(); }
    }

    public Task<(bool Ok, string Msg)> StopAsync(string controllerId)
    {
        if (!_processes.TryGetValue(controllerId, out var p) || p == null || p.HasExited)
        {
            _processes.TryRemove(controllerId, out _);
            return Task.FromResult((false, "실행 중인 프로세스 없음"));
        }
        try { p.Kill(entireProcessTree: true); } catch { }
        p.Dispose();
        _processes.TryRemove(controllerId, out _);
        _startedAt.TryRemove(controllerId, out _);
        return Task.FromResult((true, $"{controllerId} 중지됨"));
    }

    private void AppendLog(string logPath, string line)
    {
        try { File.AppendAllText(logPath, line + "\n"); } catch { }
    }

    public override void Dispose()
    {
        foreach (var (_, p) in _processes)
        {
            try { if (p != null && !p.HasExited) p.Kill(entireProcessTree: true); p?.Dispose(); } catch { }
        }
        foreach (var (_, sem) in _startLocks)
            try { sem.Dispose(); } catch { }
        base.Dispose();
    }
}
