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

namespace ExperionCrawler.Tests;

// WP3-F: docs/안전피드램프-검증시나리오매트릭스.md S0~S6 (S7은 엔진 mbState라 제외).
public class FeedRampCalculatorTests
{
    private static ColumnConfig C6111() => new()
    {
        Id = 1, Name = "C-6111", Enabled = true, FeedTag = "ficq-6101", ProductKey = "P",
        ScanSec = 2, StaleSec = 120, PressureTag = "pica-6111", TempTags = Array.Empty<string>(),
        Streams = new[]
        {
            new StreamConfig { Key = "P", FlowTag = "ficq-6118", Role = StreamRole.Commanded, TargetCoeff = 0.95, ThetaUpSec = 60, ThetaDnSec = 60, TauSec = 900, SpMin = 0, SpMax = 2000, RateUpPerMin = 30, RateDnPerMin = 60, Grade = Confidence.A },
            new StreamConfig { Key = "R", FlowTag = "ficq-6113", Role = StreamRole.Commanded, TargetCoeff = 0.80, ThetaUpSec = 0,  ThetaDnSec = 0,  TauSec = 0,   SpMin = 0, SpMax = 2000, RateUpPerMin = 30, RateDnPerMin = 30, Grade = Confidence.A },
            new StreamConfig { Key = "D", FlowTag = "ficq-6114", Role = StreamRole.LevelDriven, TargetCoeff = 0.02, SpMin = 0, SpMax = 1000, Grade = Confidence.B },
            new StreamConfig { Key = "B", FlowTag = "ficq-6116", Role = StreamRole.LevelDriven, TargetCoeff = 0.03, SpMin = 0, SpMax = 500,  Grade = Confidence.B },
        }
    };

    private static PvSnapshot Snap(double feed, bool good = true) => new(
        new TagSample("ficq-6101.pv", feed, good, DateTime.UtcNow), null,
        Array.Empty<TagSample>(),
        new Dictionary<string, TagSample>
        {
            ["P"] = new("ficq-6118.pv", 0.95 * feed, true, DateTime.UtcNow),
            ["R"] = new("ficq-6113.pv", 0.80 * feed, true, DateTime.UtcNow),
            ["D"] = new("ficq-6114.pv", 0.02 * feed, true, DateTime.UtcNow),
            ["B"] = new("ficq-6116.pv", 0.03 * feed, true, DateTime.UtcNow),
        });

    // anchor extras: pica40, pi80(ΔP40), ti6103=150, fiq6115=300
    private static RampExtraInputs Extra(double? pi6111b = 80, double ti6103 = 150, double floodLimit = double.NaN, double n = 1.8)
        => new(Pica: 40, Pi6111b: pi6111b, Ti6103: ti6103, Fiq6115: 300, Op: null, FloodLimit: floodLimit, FloodExponentN: n);

    private static void Near(double expected, double actual, double tolFrac = 0.02)
        => Assert.True(Math.Abs(actual - expected) <= Math.Abs(expected) * tolFrac + 1e-6,
            $"expected≈{expected}, actual={actual}");

    [Fact] // S0 평형
    public void S0_baseline_no_change()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900), 900, 50, 0.001, 150, Extra(), false);
        Assert.False(a.Hold);
        Near(0, a.RampTimeMin, 0); Assert.Equal(0, a.RampTimeMin);
        Near(2105.26, a.Ceiling.Value); Assert.Equal("P sp_max", a.Ceiling.Binding);
        Near(300, a.Steam.Fiq6115To!.Value);
    }

    [Fact] // S1 동특성 binding
    public void S1_dynamic_binding()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900), 1100, 50, 0.001, 150, Extra(), false);
        Assert.Equal("dynamic", a.RampRate.Binding);
        Near(3.289, a.RampRate.Value);
        Near(60.8, a.RampTimeMin);
        Near(366.7, a.Steam.Fiq6115To!.Value);
        Near(2105.26, a.Ceiling.Value);
    }

    [Fact] // S2 밸브 슬루 binding (ΔI 크게)
    public void S2_valveslew_binding()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900), 1100, 600, 0.001, 150, Extra(), false);
        Assert.Equal("valveSlew@P", a.RampRate.Binding);
        Near(31.58, a.RampRate.Value);
        Near(6.33, a.RampTimeMin);
    }

    [Fact] // S3 ceiling 초과 → 클램프
    public void S3_ceiling_clamp()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900), 2200, 50, 0.001, 150, Extra(), false);
        Near(2105.26, a.ClampedTarget);
        Assert.Equal("P sp_max", a.Ceiling.Binding);
        Assert.Equal(2200, a.TargetFeed);
    }

    [Fact] // S4 flooding ceiling
    public void S4_flooding_ceiling()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900), 1100, 50, 0.001, 150,
            Extra(pi6111b: 120, floodLimit: 100, n: 1.8), false);  // ΔPnow=80
        Assert.Equal("flooding", a.Ceiling.Binding);
        Near(1018.8, a.Ceiling.Value);
        Near(1018.8, a.ClampedTarget);
    }

    [Fact] // S5 TI-6103 하강 현열 FF (피드 무변에도 steam↑)
    public void S5_sensible_correction()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900), 900, 50, 0.001, 150,
            Extra(ti6103: 140), false);                            // 150→140, 피드 고정
        Near(309.0, a.Steam.Fiq6115To!.Value);                     // 300 + 0.001*900*10
        Assert.Equal(0, a.RampTimeMin);
    }

    [Fact] // S6 stale/HOLD
    public void S6_feed_bad_holds()
    {
        var a = FeedRampCalculator.Compute(C6111(), Snap(900, good: false), 1100, 50, 0.001, 150, Extra(), false);
        Assert.True(a.Hold);
        Assert.Contains(a.Warnings, w => w.Contains("feed-bad"));
    }
}
