using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Hc900Crawler.Infrastructure.Control;

public sealed record SteamModel
{
    [JsonPropertyName("column")]
    public string Column { get; init; } = "";

    [JsonPropertyName("features")]
    public List<string> Features { get; init; } = [];

    [JsonPropertyName("linear_coeffs")]
    public List<double> LinearCoeffs { get; init; } = [];

    [JsonPropertyName("intercept")]
    public double Intercept { get; init; }

    [JsonPropertyName("linear_r2")]
    public double LinearR2 { get; init; }

    [JsonPropertyName("gbm_r2")]
    public double? GbmR2 { get; init; }

    [JsonPropertyName("valve_poly")]
    public List<double> ValvePoly { get; init; } = [];  // c3, c2, c1, c0

    [JsonPropertyName("envelope_lo")]
    public Dictionary<string, double> EnvelopeLo { get; init; } = [];

    [JsonPropertyName("envelope_hi")]
    public Dictionary<string, double> EnvelopeHi { get; init; } = [];

    [JsonPropertyName("n_operating_points")]
    public int NOperatingPoints { get; init; }
}

public sealed record SteamAdvisoryResult
{
    public double? RecOp { get; init; }
    public double? RecSteam { get; init; }
    public string Confidence { get; init; } = "N/A";
    public string Mode { get; init; } = "UNKNOWN";
    public bool Ood { get; init; }
    public bool InEnv { get; init; }
    public double Feed { get; init; }
    public double Product { get; init; }
    public double TC { get; init; }
    public string Message { get; init; } = "";
}

public sealed class SteamAdvisor
{
    private SteamModel? _model;
    private readonly string _modelPath;
    private readonly ILogger<SteamAdvisor> _logger;

    public SteamAdvisor(IConfiguration config, ILogger<SteamAdvisor> logger)
    {
        _modelPath = config.GetValue<string>("SteamAdvisor:ModelPath")
                     ?? "/home/windpacer/projects/hc900_ax/scripts/analysis/C-6111_model.json";
        _logger = logger;
        LoadModel();
    }

    public bool IsLoaded => _model is not null;

    public void LoadModel(string? path = null)
    {
        var p = path ?? _modelPath;
        if (!File.Exists(p))
        {
            _logger.LogWarning("[SteamAdvisor] 모델 파일 없음: {Path}", p);
            return;
        }
        var json = File.ReadAllText(p);
        _model = JsonSerializer.Deserialize<SteamModel>(json);
        _logger.LogInformation("[SteamAdvisor] 모델 로드: {Column} (R²={R2})",
            _model?.Column, _model?.LinearR2);
    }

    public SteamAdvisoryResult Predict(double feed, double product, double tC)
    {
        if (_model is null)
            return new SteamAdvisoryResult { Message = "모델 미로드", Confidence = "N/A", Mode = "UNKNOWN",
                Feed = feed, Product = product, TC = tC };
        if (double.IsNaN(feed) || double.IsNaN(product) || double.IsNaN(tC))
            return new SteamAdvisoryResult { Message = "입력값에 NaN 포함", Confidence = "N/A",
                Mode = "INVALID", Feed = feed, Product = product, TC = tC };

        var mode = ClassifyMode(feed, product, tC);
        var inEnv = InEnvelope(feed, product, tC, _model);
        if (_model.LinearCoeffs.Count < 3)
        {
            _logger.LogWarning("[SteamAdvisor] LinearCoeffs 부족 ({Count}개, 3 필요)", _model.LinearCoeffs.Count);
            return new SteamAdvisoryResult { Message = "모델 계수 부족", Confidence = "N/A",
                Mode = mode, Feed = feed, Product = product, TC = tC };
        }
        var steam = _model.Intercept
                    + _model.LinearCoeffs[0] * feed
                    + _model.LinearCoeffs[1] * product
                    + _model.LinearCoeffs[2] * tC;
        var op = PolyVal(_model.ValvePoly, steam);
        op = Math.Clamp(op, 0, 100);

        if (mode != "PROD")
        {
            return new SteamAdvisoryResult
            {
                RecOp = null, RecSteam = null, Confidence = "N/A",
                Mode = mode, Ood = !inEnv, InEnv = inEnv,
                Feed = feed, Product = product, TC = tC,
                Message = $"운전모드={mode} — advisory는 PROD에서만 제공"
            };
        }

        string confidence;
        string msg;
        if (!inEnv)
        {
            confidence = "LOW_OOD";
            msg = $"⚠ 범위밖 입력 — 권장 OP={op:F1}% (외삽, 신뢰도 낮음). 오퍼레이터 판단 우선";
        }
        else
        {
            confidence = "HIGH";
            msg = $"권장 OP={op:F1}% (신뢰: 구간내)";
        }

        return new SteamAdvisoryResult
        {
            RecOp = Math.Round(op, 1), RecSteam = Math.Round(steam, 1),
            Confidence = confidence, Mode = mode, Ood = !inEnv, InEnv = inEnv,
            Feed = feed, Product = product, TC = tC, Message = msg
        };
    }

    public ValueTask<SteamAdvisoryResult> PredictAsync(double feed, double product, double tC, CancellationToken ct = default)
    {
        _ = ct;
        return ValueTask.FromResult(Predict(feed, product, tC));
    }

    private static string ClassifyMode(double feed, double product, double tC)
    {
        if (product > 100) return "PROD";
        if (tC > 60) return "LINEOUT";
        if (feed > 50) return "PROD";
        return "STOPPED";
    }

    private static bool InEnvelope(double feed, double product, double tC, SteamModel m)
    {
        if (m.EnvelopeLo.Count == 0 || m.EnvelopeHi.Count == 0) return true;
        if (feed < m.EnvelopeLo.GetValueOrDefault("feed", double.MinValue)) return false;
        if (feed > m.EnvelopeHi.GetValueOrDefault("feed", double.MaxValue)) return false;
        if (product < m.EnvelopeLo.GetValueOrDefault("product", double.MinValue)) return false;
        if (product > m.EnvelopeHi.GetValueOrDefault("product", double.MaxValue)) return false;
        if (tC < m.EnvelopeLo.GetValueOrDefault("T_C", double.MinValue)) return false;
        if (tC > m.EnvelopeHi.GetValueOrDefault("T_C", double.MaxValue)) return false;
        return true;
    }

    private double PolyVal(List<double> coeffs, double x)
    {
        if (coeffs.Count < 4)
        {
            _logger.LogWarning("[SteamAdvisor] ValvePoly 계수 부족 ({Count}개, 4 필요)", coeffs.Count);
            return x;
        }
        return coeffs[0] * x * x * x + coeffs[1] * x * x + coeffs[2] * x + coeffs[3];
    }
}
