using ExperionCrawler.Core.Application.Feedforward;
using ExperionCrawler.Infrastructure.Control;
using Xunit;

namespace ExperionCrawler.Tests;

public class FeedforwardFrontTests
{
    [Fact]
    public void Front_stable_within_band()
    {
        var ind = new FrontPositionIndicator(bandwidth: 0.3);
        for (int i = 0; i < 50; i++) ind.Update(100.0, tsSec: 2, refTauSec: 60, strongSignal: true);
        var (state, trim, grade) = ind.Update(100.1, 2, 60, true);
        Assert.Contains("정상", state);
        Assert.Null(trim);
        Assert.Equal(Confidence.B, grade);
    }

    [Fact]
    public void Front_rise_triggers_reflux_advice()
    {
        var ind = new FrontPositionIndicator(bandwidth: 0.3);
        for (int i = 0; i < 200; i++) ind.Update(100.0, tsSec: 2, refTauSec: 60, strongSignal: false);
        var (state, trim, grade) = ind.Update(105.0, 2, 60, false);
        Assert.Contains("상승", state);
        Assert.Equal("환류↑ 권장", trim);
        Assert.Equal(Confidence.C, grade);
    }

    [Fact]
    public void Front_fall_triggers_boilup_advice()
    {
        var ind = new FrontPositionIndicator(bandwidth: 0.3);
        for (int i = 0; i < 200; i++) ind.Update(100.0, tsSec: 2, refTauSec: 60, strongSignal: true);
        var (state, trim, _) = ind.Update(95.0, 2, 60, true);
        Assert.Contains("하강", state);
        Assert.Contains("boilup", trim);
    }

    // ── ApplyFront 부호 규약 (브레인스토밍 §10.2-A 버그픽스 회귀) ──────────
    // temp_tags = [A하단 … D상단], 정상 A>B>C>D 단조감소.
    // front metric은 "상단−하단"이어야 함: 프론트 상승(heavies↑→상단 가열) 시 metric↑ → dev>0 → 상승/환류↑.
    private static ColumnConfig FrontCfg() => new()
    {
        Id = 1, Name = "C-FRONT", Enabled = true, FeedTag = "f", ProductKey = "P", ScanSec = 2,
        DTdP = 0.0, PRef = double.NaN, PressureTag = null,            // 압력경로 비활성 → pUnstable 없음
        FeedMoveThresholdPerMin = 0.0,                               // moving 비활성 → transient 없음
        TempTags = new[] { "a", "b", "c", "d" },
        Streams = new[] { new StreamConfig { Key = "P", FlowTag = "ficq-6118", Role = StreamRole.Commanded, Grade = Confidence.A, TargetCoeff = 0.95 } }
    };

    private static PvSnapshot FrontSnap(double a, double b, double c, double d) => new(
        new TagSample("f", 100, true, DateTime.UtcNow),
        null,
        Array.Empty<TagSample>(),
        new Dictionary<string, TagSample> { ["P"] = new TagSample("ficq-6118", 95, true, DateTime.UtcNow) })
        {
            Temps = new[]
            {
                new TagSample("a", a, true, DateTime.UtcNow), new TagSample("b", b, true, DateTime.UtcNow),
                new TagSample("c", c, true, DateTime.UtcNow), new TagSample("d", d, true, DateTime.UtcNow)
            }
        };

    [Fact]
    public void ApplyFront_top_warming_is_front_rise_reflux_advice()
    {
        var engine = new FeedforwardEngine();
        var st = new ColumnState();
        // 정상 단조감소(A=110>B>C>D=90)로 baseline 시드
        for (int i = 0; i < 5; i++) engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 90), st, DateTime.UtcNow);
        // 상단(D) 가열 = heavies 상승 = 프론트 상승 → 상승/환류↑ (버그였다면 하강/boilup로 반전)
        var res = engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 100), st, DateTime.UtcNow);
        Assert.Contains("상승", res.FrontPositionState);
        Assert.Equal("환류↑ 권장", res.FrontTrimAdvice);
    }

    [Fact]
    public void ApplyFront_top_cooling_is_front_fall_boilup_advice()
    {
        var engine = new FeedforwardEngine();
        var st = new ColumnState();
        for (int i = 0; i < 5; i++) engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 90), st, DateTime.UtcNow);
        // 상단(D) 추가 냉각 = 프론트 하강 → 하강/boilup
        var res = engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 80), st, DateTime.UtcNow);
        Assert.Contains("하강", res.FrontPositionState);
        Assert.Contains("boilup", res.FrontTrimAdvice);
    }
}
