using System;
using System.Collections.Generic;
using System.Linq;
using ExperionCrawler.Core.Application.Feedforward;
using ExperionCrawler.Infrastructure.Control;
using Xunit;

namespace ExperionCrawler.Tests;

public class FeedforwardRecoveryTests
{
    private static ColumnConfig Cfg(bool autoArm) => new()
    {
        Id = 1, Name = "C-REC", Enabled = true, FeedTag = "f", ProductKey = "P",
        ScanSec = 2, BiasMaWindowSec = 4,
        RecoveryEnabled = true, RecoveryAutoArm = autoArm,
        ImbalanceTriggerFrac = 0.10, ImbalanceTriggerSec = 4,
        RecoverySettleSec = 4, ReturnRampSec = 4, FeedRecoverySp = 0,
        Streams = new[]
        {
            new StreamConfig { Key="P", FlowTag="p", Role=StreamRole.Commanded,   Grade=Confidence.A, TargetCoeff=0.95, SpMax=950 },
            new StreamConfig { Key="R", FlowTag="r", Role=StreamRole.Commanded,   Grade=Confidence.A, TargetCoeff=0.80, SpMax=1100, RefluxFromProduct=true },
            new StreamConfig { Key="D", FlowTag="d", Role=StreamRole.LevelDriven, Grade=Confidence.B, TargetCoeff=0.02, SpMax=60 },
            new StreamConfig { Key="B", FlowTag="b", Role=StreamRole.LevelDriven, Grade=Confidence.B, TargetCoeff=0.03, SpMax=80 },
        }
    };

    private static PvSnapshot Imbalanced() => new(
        new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty<TagSample>(),
        new Dictionary<string, TagSample> {
            ["P"]=new("p",30,true,DateTime.UtcNow), ["R"]=new("r",50,true,DateTime.UtcNow),
            ["D"]=new("d",2,true,DateTime.UtcNow),  ["B"]=new("b",3,true,DateTime.UtcNow) });

    private static PvSnapshot Balanced() => new(
        new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty<TagSample>(),
        new Dictionary<string, TagSample> {
            ["P"]=new("p",95,true,DateTime.UtcNow), ["R"]=new("r",760,true,DateTime.UtcNow),
            ["D"]=new("d",2,true,DateTime.UtcNow),  ["B"]=new("b",3,true,DateTime.UtcNow) });

    [Fact]
    public void AutoArm_enters_recovering_on_sustained_imbalance()
    {
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        AdvisoryResult res = engine.Tick(Cfg(autoArm:true), Imbalanced(), st, DateTime.UtcNow);
        for (int i = 0; i < 6; i++) res = engine.Tick(Cfg(autoArm:true), Imbalanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Recovering, res.Mode);
        Assert.Equal(0.0, res.FeedRecommendedSp);
        var r = res.Streams.First(s => s.Key == "R");
        var p = res.Streams.First(s => s.Key == "P");
        Assert.Equal(1100.0, r.RecommendedSp);
        Assert.Equal(0.0, p.RecommendedSp);
        Assert.False(p.Valid);
    }

    [Fact]
    public void ManualArm_required_when_autoArm_false()
    {
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        for (int i = 0; i < 6; i++) engine.Tick(Cfg(autoArm:false), Imbalanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Normal, st.Mode);
        st.OperatorArmed = true;
        var res = engine.Tick(Cfg(autoArm:false), Imbalanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Recovering, res.Mode);
    }

    [Fact]
    public void Recovers_then_returns_to_normal()
    {
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        for (int i = 0; i < 6; i++) engine.Tick(Cfg(autoArm:true), Imbalanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Recovering, st.Mode);
        AdvisoryResult res = null!;
        for (int i = 0; i < 10; i++) res = engine.Tick(Cfg(autoArm:true), Balanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Normal, res.Mode);
    }

    [Fact]
    public void Cancel_returns_to_normal_immediately()
    {
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        for (int i = 0; i < 6; i++) engine.Tick(Cfg(autoArm:true), Imbalanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Recovering, st.Mode);
        st.OperatorCancel = true;
        var res = engine.Tick(Cfg(autoArm:true), Imbalanced(), st, DateTime.UtcNow);
        Assert.Equal(ColumnMode.Normal, res.Mode);
    }

    // ── WP4: 온도역전 + vloss 코러보레이션 ────────────────────────────
    // temp_tags 하단→상단: 정상 단조감소(110>105>100>90). 역전은 하단이 상단보다 차가워야 하므로
    // 하단(110) > 중간(100) > 상단(105 → 역전: 100-105 < -tolInv)
    // 열순서: A(tica-6111a)=리보일러→B(ti-6111b)=중간→C(ti-6111c)=제품→D(ti-6111d,제외)

    // 조성트레이(A,B,C) 역전: 태그 D(ti-6111d)는 제외되므로, B(ti-6111b)가 C(ti-6111c)보다
    // 차가우면 역전(B-C): B=95, C=100 → 95-100=-5 < -0.5 → 온도역전
    private static PvSnapshot ImbalancedWithInversion() => new(
        new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty<TagSample>(),
        new Dictionary<string, TagSample> {
            ["P"]=new("p",30,true,DateTime.UtcNow), ["R"]=new("r",50,true,DateTime.UtcNow),
            ["D"]=new("d",2,true,DateTime.UtcNow),  ["B"]=new("b",3,true,DateTime.UtcNow) })
        { Temps = new[] {
            new TagSample("tica-6111a", 110, true, DateTime.UtcNow),   // 하단 A
            new TagSample("ti-6111b",   95,  true, DateTime.UtcNow),   // B (C보다 차가움 → B-C 역전)
            new TagSample("ti-6111c",   100, true, DateTime.UtcNow),   // C
            new TagSample("ti-6111d",   90,  true, DateTime.UtcNow) }};// D(제외)

    private static PvSnapshot BalancedWithInversion() => new(
        new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty<TagSample>(),
        new Dictionary<string, TagSample> {
            ["P"]=new("p",95,true,DateTime.UtcNow),  ["R"]=new("r",760,true,DateTime.UtcNow),
            ["D"]=new("d",2,true,DateTime.UtcNow),   ["B"]=new("b",3,true,DateTime.UtcNow) })
        { Temps = new[] {
            new TagSample("tica-6111a", 110, true, DateTime.UtcNow),   // A
            new TagSample("ti-6111b",   95,  true, DateTime.UtcNow),   // B (C보다 차가움 → B-C 역전)
            new TagSample("ti-6111c",   100, true, DateTime.UtcNow),   // C
            new TagSample("ti-6111d",   90,  true, DateTime.UtcNow) }};// D(제외)

    // D(ti-6111d) 제외 후 A=110, B=95, C=100 → B-C = 95-100 = -5 < -0.5 → 온도역전(B-C)
    // + vloss 불일치(Balanced=0) → corroborated=false → severe 아님

    private static PvSnapshot BalancedNormalTemps() => new(
        new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty<TagSample>(),
        new Dictionary<string, TagSample> {
            ["P"]=new("p",95,true,DateTime.UtcNow),  ["R"]=new("r",760,true,DateTime.UtcNow),
            ["D"]=new("d",2,true,DateTime.UtcNow),   ["B"]=new("b",3,true,DateTime.UtcNow) })
        { Temps = new[] {
            new TagSample("tica-6111a", 110, true, DateTime.UtcNow),   // 하단
            new TagSample("ti-6111b",   105, true, DateTime.UtcNow),   // 중간
            new TagSample("ti-6111c",   100, true, DateTime.UtcNow),   // 제품 pivot
            new TagSample("ti-6111d",   90,  true, DateTime.UtcNow) }};// 탑정

    [Fact]
    public void Inversion_with_vloss_triggers_recovery()
    {
        // WP4 역전(vloss 코러보) → severe → 전환류 진입
        var cfg = Cfg(autoArm: true) with
        {
            TempTags = new[] { "tica-6111a", "ti-6111b", "ti-6111c", "ti-6111d" },
            DTdP = 0.0, PRef = double.NaN, PressureTag = null
        };
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        // 먼저 정상 온도로 시드(spanRef)
        var s0 = engine.Tick(cfg, BalancedNormalTemps(), st, DateTime.UtcNow);
        // 역전 + vloss 상태 지속 → timer 누적 → Recovering
        string? entryReason = null;
        string? firstTp = null;
        AdvisoryResult res = null!;
        for (int i = 0; i < 6; i++)
        {
            res = engine.Tick(cfg, ImbalancedWithInversion(), st, DateTime.UtcNow);
            if (i == 0) firstTp = res.TempProfileState;
            if (res.Mode == ColumnMode.Recovering && entryReason is null)
                entryReason = res.ModeReason;
        }
        Assert.Equal("온도역전", firstTp);
        Assert.Equal(ColumnMode.Recovering, res.Mode);
        Assert.Contains("온도역전", entryReason ?? "");
    }

    [Fact]
    public void Inversion_alone_not_severe_sensor_check()
    {
        // WP4 역전만(balanced, vloss=0, ΔP 없음) → severe 아님, 센서 점검 메시지
        var cfg = Cfg(autoArm: true) with
        {
            TempTags = new[] { "tica-6111a", "ti-6111b", "ti-6111c", "ti-6111d" },
            DTdP = 0.0, PRef = double.NaN, PressureTag = null
        };
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        // 정상 온도로 시드
        engine.Tick(cfg, BalancedNormalTemps(), st, DateTime.UtcNow);
        // 역전 + balanced(no vloss) → timer 누적 안 됨 → Normal + 센서 점검
        for (int i = 0; i < 6; i++)
        {
            var res = engine.Tick(cfg, BalancedWithInversion(), st, DateTime.UtcNow);
            if (i == 5)
            {
                Assert.Equal(ColumnMode.Normal, res.Mode);
                Assert.Contains("센서 점검", res.ModeReason ?? "");
            }
        }
    }
}
