using System.Text.Json;
using System.Text.Json.Serialization;
using Hc900Crawler.Infrastructure.Control;
using Hc900Crawler.Infrastructure.Database;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Hc900Crawler.Web.Controllers;

[ApiController]
[Route("api/steam")]
public sealed class SteamAdvisorController : ControllerBase
{
    private static readonly Dictionary<string, string> _tagDescs = new(StringComparer.OrdinalIgnoreCase);

    private readonly SteamAdvisor _advisor;
    private readonly Hc900DbContext _ctx;
    private readonly IConfiguration _config;

    public SteamAdvisorController(SteamAdvisor advisor, Hc900DbContext ctx, IConfiguration config)
    {
        _advisor = advisor;
        _ctx = ctx;
        _config = config;
        EnsureTagDescs();
    }

    private static void EnsureTagDescs()
    {
        if (_tagDescs.Count > 0) return;
        try
        {
            var path = "/home/windpacer/projects/hc900_ax/docs/register-map.json";
            if (!System.IO.File.Exists(path)) return;
            var json = System.IO.File.ReadAllText(path);
            using var doc = JsonDocument.Parse(json);
            foreach (var reg in doc.RootElement.GetProperty("registers").EnumerateArray())
            {
                var tag = reg.GetProperty("tag").GetString();
                var desc = reg.GetProperty("description").GetString();
                if (tag != null && desc != null)
                    _tagDescs[tag] = desc;
            }
        }
        catch { /* non-fatal */ }
    }

    private string? GetDesc(string? tag) => tag != null && _tagDescs.TryGetValue(tag, out var d) ? d : null;

    [HttpGet("health")]
    public IActionResult Health()
    {
        return Ok(new { loaded = _advisor.IsLoaded });
    }

    [HttpGet("predict")]
    public IActionResult Predict(
        [FromQuery] double feed,
        [FromQuery] double product,
        [FromQuery] double tC)
    {
        var result = _advisor.Predict(feed, product, tC);
        return Ok(result);
    }

    [HttpPost("predict")]
    public IActionResult PredictPost([FromBody] SteamPredictBody body)
    {
        var result = _advisor.Predict(body.Feed, body.Product, body.TC);
        return Ok(result);
    }

    [HttpGet("models")]
    public IActionResult ListModels()
    {
        var modelDir = _config.GetValue<string>("SteamAdvisor:ModelDir")
                       ?? "/home/windpacer/projects/hc900_ax/scripts/analysis";
        if (!Directory.Exists(modelDir))
            return Ok(new { columns = Array.Empty<string>() });

        var columns = Directory.GetFiles(modelDir, "*_model.json")
            .Select(Path.GetFileNameWithoutExtension)
            .Where(n => n != null)
            .Select(n => n!.Replace("_model", ""))
            .OrderBy(x => x)
            .ToList();

        var configured = _config.GetSection("SteamAdvisor:Columns").GetChildren()
            .Select(c => c.Key)
            .ToHashSet();

        var defaultCol = _config.GetValue<string>("SteamAdvisor:DefaultColumn") ?? "C-6111";
        return Ok(new { columns, configured = configured.OrderBy(x => x).ToList(), defaultColumn = defaultCol });
    }

    [HttpGet("backtest/{col}")]
    public async Task<IActionResult> Backtest(string col)
    {
        var plotDir = _config.GetValue<string>("SteamAdvisor:PlotDataDir")
                      ?? "/home/windpacer/projects/hc900_ax/scripts/analysis";
        var path = Path.Combine(plotDir, $"{col}_plotdata.json");
        if (!System.IO.File.Exists(path))
            return NotFound(new { error = $"플롯데이터 없음: {col}" });

        try
        {
            var json = await System.IO.File.ReadAllTextAsync(path);
            return Content(json, "application/json", System.Text.Encoding.UTF8);
        }
        catch (Exception ex)
        {
            return StatusCode(500, new { error = $"플롯데이터 읽기 실패: {ex.Message}" });
        }
    }

    [HttpGet("live")]
    public async Task<IActionResult> Live([FromQuery] string? col = null)
    {
        col ??= _config.GetValue<string>("SteamAdvisor:DefaultColumn") ?? "C-6111";
        var tags = _config.GetSection($"SteamAdvisor:Columns:{col}");
        var feedTag = tags["Feed"];
        var productTag = tags["Product"];
        var tcTag = tags["TC"];
        var steamOpTag = tags["SteamOp"];
        var steamFlowTag = tags["SteamFlow"];

        if (string.IsNullOrEmpty(feedTag) || string.IsNullOrEmpty(productTag) || string.IsNullOrEmpty(tcTag))
            return BadRequest(new { error = $"컬럼 {col} 태그 매핑 불완전" });

        var tagNames = new[] { feedTag, productTag, tcTag };
        if (!string.IsNullOrEmpty(steamOpTag)) tagNames = tagNames.Append(steamOpTag).ToArray();
        if (!string.IsNullOrEmpty(steamFlowTag)) tagNames = tagNames.Append(steamFlowTag).ToArray();

        var live = await _ctx.RealtimePoints
            .Where(r => tagNames.Contains(r.TagName))
            .GroupBy(r => r.TagName)
            .ToDictionaryAsync(g => g.Key, g => g.OrderByDescending(r => r.Timestamp).FirstOrDefault()?.LiveValue);

        if (!live.ContainsKey(feedTag) || !live.ContainsKey(productTag) || !live.ContainsKey(tcTag))
        {
            var missing = new[] { feedTag, productTag, tcTag }
                .Where(t => !live.ContainsKey(t))
                .ToList();
            return Ok(new { col, status = "missing_tags", missing,
                message = "일부 태그 실시간값 없음 — 게이트웨이 폴링 확인" });
        }

        var feed = double.TryParse(live[feedTag], out var f) ? f : double.NaN;
        var product = double.TryParse(live[productTag], out var p) ? p : double.NaN;
        var tC = double.TryParse(live[tcTag], out var tc) ? tc : double.NaN;

        var result = _advisor.Predict(feed, product, tC);

        double? steamOp = null;
        if (!string.IsNullOrEmpty(steamOpTag) && live.TryGetValue(steamOpTag, out var sopStr) && double.TryParse(sopStr, out var sop))
            steamOp = sop;
        double? steamFlow = null;
        if (!string.IsNullOrEmpty(steamFlowTag) && live.TryGetValue(steamFlowTag, out var sfStr) && double.TryParse(sfStr, out var sf))
            steamFlow = sf;

        return Ok(new
        {
            col,
            result.RecOp, result.RecSteam, result.Confidence,
            result.Mode, result.Ood, result.InEnv, result.Message,
            Feed = result.Feed,
            Product = result.Product,
            TC = result.TC,
            ActualOp = steamOp,
            ActualSteamFlow = steamFlow,
            Tags = new { feed = feedTag, product = productTag, tC = tcTag, steamOp = steamOpTag, steamFlow = steamFlowTag },
            Descs = new { feed = GetDesc(feedTag), product = GetDesc(productTag), tC = GetDesc(tcTag), steamOp = GetDesc(steamOpTag), steamFlow = GetDesc(steamFlowTag) },
            Timestamp = DateTime.UtcNow,
        });
    }

    // ── 레이어②: 컬럼 온도 프로파일 이격 모니터 ────────────────────────
    // realtime 단별 온도/진공 vs 기준밴드({col}_tempref.json) → 제품매칭 + z-score 이격.
    // col 규약 = 컬럼키("C-6111","C-6211","C-8111",...).
    [HttpGet("tempprofile/{col}")]
    public async Task<IActionResult> TempProfile(string col)
    {
        var dir = _config.GetValue<string>("SteamAdvisor:ModelDir")
                  ?? "/home/windpacer/projects/hc900_ax/scripts/analysis";
        var path = Path.Combine(dir, $"{col}_tempref.json");
        if (!System.IO.File.Exists(path))
            return NotFound(new { error = $"기준 프로파일 없음: {col}_tempref.json" });

        var tref = JsonSerializer.Deserialize<TempRef>(
            await System.IO.File.ReadAllTextAsync(path),
            new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
        if (tref is null) return StatusCode(500, new { error = "tempref 파싱 실패" });

        var tagMap = TagsFor(ToSuffix(col));
        var tagNames = tagMap.Values.ToArray();
        var live = await _ctx.RealtimePoints
            .Where(r => tagNames.Contains(r.TagName))
            .GroupBy(r => r.TagName)
            .ToDictionaryAsync(g => g.Key, g => g.OrderByDescending(r => r.Timestamp).FirstOrDefault()?.LiveValue);
        double? Val(string tag)
            => live.TryGetValue(tag, out var s) && double.TryParse(s, out var v) ? v : null;
        var cur = tagMap.ToDictionary(kv => kv.Key, kv => Val(kv.Value));

        // 제품 매칭: 현재 reb_temp에 가장 가까운 기준 제품(온도프로파일=제품 식별자)
        TempProduct? prod = null;
        if (cur["reb_temp"] is double reb && tref.Products.Count > 0)
            prod = tref.Products
                .OrderBy(pr => Math.Abs((pr.Stages.GetValueOrDefault("reb_temp")?.Median ?? 1e9) - reb))
                .First();

        static double? Z(double? c, TempStat? r)
            => (c is double cv && r is not null && r.Std > 1e-6) ? (cv - r.Median) / r.Std : null;

        var stages = tref.StagesOrder.Select(s =>
        {
            var c = cur.GetValueOrDefault(s);
            var rs = prod?.Stages.GetValueOrDefault(s);
            var z = Z(c, rs);
            return (object)new
            {
                stage = s, current = c, refMedian = rs?.Median, refStd = rs?.Std,
                z, deviated = z is double zz && Math.Abs(zz) > 2
            };
        }).ToList();

        var vac = cur.GetValueOrDefault("vacuum");
        var vz = Z(vac, prod?.Vacuum);
        double? spanAD = (cur["reb_temp"] is double r2 && cur["T_D"] is double td) ? r2 - td : null;

        return Ok(new
        {
            column = tref.Column,
            period = tref.Period,
            matchedProduct = prod?.Label,
            nProducts = tref.NProducts,
            stages,
            vacuum = new
            {
                current = vac, refMedian = prod?.Vacuum.Median, refStd = prod?.Vacuum.Std,
                z = vz, deviated = vz is double v && Math.Abs(v) > 2
            },
            spanAD,
            spanRef = prod?.SpanAD,
            products = tref.Products,
            timestamp = DateTime.UtcNow,
        });
    }

    // roles_for(C# 미러) — 단별 온도/진공 태그. c6111_extract COLUMN_EXCEPTIONS 대응.
    // p = numeric suffix ("6111", "6211", "8111", ...)
    private static Dictionary<string, string> TagsFor(string p)
    {
        var m = new Dictionary<string, string>
        {
            ["reb_temp"] = $"TICA-{p}A.PV",
            ["T_B"] = $"TI-{p}B.PV",
            ["T_C"] = $"TI-{p}C.PV",
            ["T_D"] = $"TI-{p}D.PV",
            ["vacuum"] = $"PICA-{p}.PV",
        };
        switch (p)
        {
            case "8111": m["reb_temp"] = "TICA-8111.PV"; m["vacuum"] = "PICA-8111A.PV"; break;
            case "9111": m["vacuum"] = "PICA-9111A.PV"; break;
            case "9211": m["vacuum"] = "PICA-9211A.PV"; break;
            case "10111": m["vacuum"] = "PICA-10111A.PV"; break;
            case "10211": m["vacuum"] = "PICA-10211A.PV"; break;
        }
        return m;
    }

    // 컬럼키 "C-6111" → numeric suffix "6111"
    private static string ToSuffix(string col) => col.StartsWith("C-") ? col[2..] : col;
}

// ── tempref.json 역직렬화 (gen_temp_profiles.py 산출 구조) ──
public sealed record TempRef
{
    public string Column { get; init; } = "";
    [JsonPropertyName("stages_order")] public List<string> StagesOrder { get; init; } = [];
    [JsonPropertyName("n_products")] public int NProducts { get; init; }
    public string Period { get; init; } = "";
    public List<TempProduct> Products { get; init; } = [];
}

public sealed record TempProduct
{
    public string Label { get; init; } = "";
    [JsonPropertyName("n_rows")] public int NRows { get; init; }
    [JsonPropertyName("span_AD")] public double SpanAD { get; init; }
    public TempStat Vacuum { get; init; } = new();
    public Dictionary<string, TempStat> Stages { get; init; } = [];
}

public sealed record TempStat
{
    public double Median { get; init; }
    public double Std { get; init; }
}

public sealed record SteamPredictBody
{
    public double Feed { get; init; }
    public double Product { get; init; }
    public double TC { get; init; }
}
