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

namespace ExperionCrawler.Tests;

// WP5 2·3단계: 조성 base(분율×feed) + 유계 trim(±5%). B=하부front, D=상부front.
public class FeedforwardCompositionTrimTests
{
    private static ColumnConfig Cfg() => new()
    {
        Id = 1, Name = "C-CT", Enabled = true, FeedTag = "f", ProductKey = "P", ScanSec = 2,
        DTdP = 0.0, PRef = double.NaN, PressureTag = null, FeedMoveThresholdPerMin = 0.0,
        TempTags = new[] { "a", "b", "c", "d" },
        Streams = new[]
        {
            new StreamConfig { Key = "P", FlowTag = "ficq-6118", Role = StreamRole.Commanded, Grade = Confidence.A, TargetCoeff = 0.95 },
            new StreamConfig { Key = "B", FlowTag = "ficq-6116", Role = StreamRole.LevelDriven, Grade = Confidence.B, TargetCoeff = 0.03 },
        }
    };

    private static PvSnapshot Snap(double feed, double a, double b, double c, double d) => new(
        new TagSample("f", feed, true, DateTime.UtcNow), null, Array.Empty<TagSample>(),
        new Dictionary<string, TagSample>
        {
            ["P"] = new TagSample("ficq-6118", 0.95 * feed, true, DateTime.UtcNow),
            ["B"] = new TagSample("ficq-6116", 0.03 * feed, 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)
            }
        };

    private static StreamAdvisory B(AdvisoryResult r) => r.Streams.First(s => s.Key == "B");

    [Fact]
    public void Base_is_fraction_times_feed_no_trim_when_steady()
    {
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        var r = engine.Tick(Cfg(), Snap(900, 110, 105, 100, 90), st, DateTime.UtcNow);
        var b = B(r);
        Assert.Equal(27.0, b.CompositionBase!.Value, 3);              // 0.03×900
        // 첫 tick: baseline 시드 → dev≈0 → trim≈0
        Assert.Equal(27.0, b.RecommendedSpComposition!.Value, 1);
    }

    [Fact]
    public void Lower_front_rise_adds_positive_trim_clamped()
    {
        var engine = new FeedforwardEngine(); var st = new ColumnState();
        for (int i = 0; i < 5; i++) engine.Tick(Cfg(), Snap(900, 110, 105, 100, 90), st, DateTime.UtcNow);
        // 하부 front 상승: 중질이 제품(C)쪽 침투 → C 가열(100→104) → C−B↑(devLo>0) → B trim>0.
        // (단조 A110≥B105≥C104 유지 → 역전 미트리거)
        var r = engine.Tick(Cfg(), Snap(900, 110, 105, 104, 90), st, DateTime.UtcNow);
        var b = B(r);
        Assert.True(b.Trim!.Value > 0, $"trim should be >0, got {b.Trim}");
        Assert.True(b.Trim!.Value <= 0.05 * 27.0 + 1e-9, "±5% clamp");   // ≤ 1.35
        Assert.Equal(27.0 + b.Trim.Value, b.RecommendedSpComposition!.Value, 6);
    }

    [Fact]
    public void Transient_blocks_trim()
    {
        var engine = new FeedforwardEngine();
        var cfg = Cfg() with { FeedMoveThresholdPerMin = 1.0 };   // 이동 감지 활성
        var st = new ColumnState();
        engine.Tick(cfg, Snap(900, 110, 105, 100, 90), st, DateTime.UtcNow);
        var r = engine.Tick(cfg, Snap(1100, 110, 90, 100, 90), st, DateTime.UtcNow); // 큰 feed 이동→transient
        var b = B(r);
        Assert.Equal(0.0, b.Trim ?? 0.0, 6);   // 과도 시 trim 0
    }

    [Fact]
    public void Composition_store_fraction_overrides_default()
    {
        var store = new CompositionStore();
        store.Set(1, "B", 0.05);
        Assert.True(store.TryGet(1, "b", out var f));   // 대소문자 무시
        Assert.Equal(0.05, f, 6);
    }
}
