using Hc900Crawler.Core.Application.Feedforward;
using Hc900Crawler.Core.Application.Interfaces;
using Hc900Crawler.Core.Domain.Entities;
using Hc900Crawler.Infrastructure.Hc900;
using Hc900Crawler.Infrastructure.Kb;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace Hc900Crawler.Web.Controllers;

[ApiController]
[Route("api/ff")]
public sealed class FeedforwardController : ControllerBase
{
    private readonly IFeedforwardAdvisoryStore _store;
    private readonly IFeedforwardConfigStore   _config;
    private readonly IFeedforwardAuditService  _audit;
    private readonly IFeedforwardWriteGuard    _writeGuard;
    private readonly Hc900WriteService         _writeClient;
    private readonly IKbAuthService            _auth;
    private readonly Microsoft.Extensions.Configuration.IConfiguration _appConfig;
    private readonly Hc900Crawler.Infrastructure.Control.FeedforwardSupervisor _supervisor;
    public FeedforwardController(
        IFeedforwardAdvisoryStore store,
        IFeedforwardConfigStore   config,
        IFeedforwardAuditService  audit,
        IFeedforwardWriteGuard    writeGuard,
        Hc900WriteService         writeClient,
        IKbAuthService            auth,
        Microsoft.Extensions.Configuration.IConfiguration appConfig,
        Hc900Crawler.Infrastructure.Control.FeedforwardSupervisor supervisor,
        ISimOverrideStore sim,
        Hc900Crawler.Infrastructure.Control.FeedRampAdvisorService ramp,
        ICompositionStore composition)
    { _store = store; _config = config; _audit = audit; _writeGuard = writeGuard;
      _writeClient = writeClient; _auth = auth; _appConfig = appConfig; _supervisor = supervisor;
      _sim = sim; _ramp = ramp; _composition = composition; }

    private readonly ISimOverrideStore _sim;
    private readonly Hc900Crawler.Infrastructure.Control.FeedRampAdvisorService _ramp;
    private readonly ICompositionStore _composition;

    private async Task<bool> AuthAsync(CancellationToken ct)
        => await _auth.ValidateAsync(Request.Headers["X-Kb-Token"].ToString(), ct);

    // ── 설정 CRUD ──
    [HttpGet("config")]
    public async Task<IActionResult> GetConfig(CancellationToken ct)
    {
        var cols = await _config.LoadAllAsync(ct);
        return Ok(new { columns = cols.Select(MapConfig) });
    }

    [HttpPost("config")]
    public async Task<IActionResult> SaveConfig([FromBody] ColumnConfig body, CancellationToken ct)
    {
        if (!await AuthAsync(ct)) return Unauthorized(new { error = "X-Kb-Token 인증 필요" });
        var id = await _config.SaveColumnAsync(body, ct);
        return Ok(new { success = true, id });
    }

    [HttpDelete("config/{id:int}")]
    public async Task<IActionResult> DeleteConfig(int id, CancellationToken ct)
    {
        if (!await AuthAsync(ct)) return Unauthorized(new { error = "X-Kb-Token 인증 필요" });
        await _config.DeleteColumnAsync(id, ct);
        return Ok(new { success = true });
    }

    // ── WO-6 전환류 ARM/취소 ──
    [HttpPost("recovery/{id:int}/arm")]
    public IActionResult ArmRecovery(int id) => Ok(new { success = _supervisor.Arm(id) });

    [HttpPost("recovery/{id:int}/cancel")]
    public IActionResult CancelRecovery(int id) => Ok(new { success = _supervisor.Cancel(id) });

    // ── Phase II: 수동 SP 쓰기 ──
    [HttpPost("write/{columnId:int}/{streamKey}")]
    public async Task<IActionResult> WriteSp(int columnId, string streamKey, [FromBody] WriteSpBody body, CancellationToken ct)
    {
        if (!await AuthAsync(ct)) return Unauthorized(new { error = "X-Kb-Token 인증 필요" });
        var advisory = _store.Get(columnId);
        if (advisory is null) return NotFound(new { error = "advisory 없음" });
        var cfg = (await _config.LoadAllAsync(ct)).FirstOrDefault(c => c.Id == columnId);
        if (cfg is null) return NotFound(new { error = "config 없음" });
        var sc = cfg.Streams.FirstOrDefault(s => s.Key == streamKey);
        if (sc is null) return NotFound(new { error = "stream 없음" });
        if (string.IsNullOrWhiteSpace(sc.SpNodeId))
            return BadRequest(new { error = "SP NodeId 미지정 — 설정에서 입력하세요" });
        var adv = advisory.Streams.FirstOrDefault(a => a.Key == streamKey);
        if (adv is null) return NotFound(new { error = "stream advisory 없음" });

        double spVal = body.value ?? (adv.RecommendedSp ?? double.NaN);
        if (double.IsNaN(spVal)) return BadRequest(new { error = "SP 값 없음" });

        // WriteGuard 검증
        var check = _writeGuard.Check(cfg, adv, sc, advisory);
        if (!check.Allowed)
            return BadRequest(new { error = $"WriteGuard 차단: {check.Reason}" });

        // HC900 gRPC 쓰기 (SpNodeId를 HC900 태그명으로 사용)
        var (success, error) = await _writeClient.WriteTagAsync("HC1", sc.SpNodeId, spVal);

        // 감사 로그
        await _audit.LogAsync(new FfActionLogEntry(columnId, "sp_write",
            StreamKey: streamKey, SpValue: spVal, NodeId: sc.SpNodeId,
            Result: success ? "success" : $"error: {error}",
            OperatorName: "manual"), ct);

        if (!success)
            return StatusCode(502, new { error = $"HC900 쓰기 실패: {error}" });

        return Ok(new { success = true, streamKey, nodeId = sc.SpNodeId, value = spVal });
    }

    // ── Phase II: 감사 로그 조회 ──
    [HttpGet("audit")]
    public async Task<IActionResult> GetAudit([FromQuery] int? columnId, [FromQuery] int limit = 50, CancellationToken ct = default)
    {
        if (!await AuthAsync(ct)) return Unauthorized(new { error = "X-Kb-Token 인증 필요" });
        var rows = await _audit.QueryAsync(columnId, limit, ct);
        return Ok(new { rows });
    }

    private object MapConfig(ColumnConfig c) => new
    {
        id = c.Id, name = c.Name, enabled = c.Enabled, advisoryOnly = c.AdvisoryOnly,
        feedTag = c.FeedTag, pressureTag = c.PressureTag, levelTags = c.LevelTags,
        scanSec = c.ScanSec, feedFilterTauSec = c.FeedFilterTauSec,
        feedMoveThresholdPerMin = c.FeedMoveThresholdPerMin, pressFilterTauSec = c.PressFilterTauSec,
        pressureBand = c.PressureBand, settleSec = c.SettleSec, staleSec = c.StaleSec, productKey = c.ProductKey,
        tempTags = c.TempTags, sensitiveTrayTag = c.SensitiveTrayTag,
        dtdp = c.DTdP, pRef = double.IsNaN(c.PRef) ? (double?)null : c.PRef,
        steamOpTag = c.SteamOpTag, thetaAutoTune = c.ThetaAutoTune,
        biasMaWindowSec = c.BiasMaWindowSec,
        recoveryEnabled = c.RecoveryEnabled, recoveryAutoArm = c.RecoveryAutoArm,
        imbalanceTriggerFrac = c.ImbalanceTriggerFrac, imbalanceTriggerSec = c.ImbalanceTriggerSec,
        recoverySettleSec = c.RecoverySettleSec, returnRampSec = c.ReturnRampSec,
        feedRecoverySp = c.FeedRecoverySp,
        deltaPTag = c.DeltaPTag, deltaPFloodLimit = c.DeltaPFloodLimit,
        tempHighLimit = c.TempHighLimit,
        streams = c.Streams.Select(s => new
        {
            key = s.Key, flowTag = s.FlowTag, role = s.Role.ToString(), levelTag = s.LevelTag, targetCoeff = s.TargetCoeff,
            thetaUpSec = s.ThetaUpSec, thetaDnSec = s.ThetaDnSec, tauSec = s.TauSec,
            spMin = s.SpMin, spMax = s.SpMax, rateUpPerMin = s.RateUpPerMin, rateDnPerMin = s.RateDnPerMin,
            refluxFromProduct = s.RefluxFromProduct, grade = s.Grade.ToString(),
            isReflux = s.IsReflux, recoverySp = double.IsNaN(s.RecoverySp) ? (double?)null : s.RecoverySp,
            spNodeId = s.SpNodeId
        })
    };

    // ── WP3: Feed Ramp Advisor (read-only) ────────────────────────
    [HttpGet("ramp-advisor")]
    public async Task<IActionResult> RampAdvisor(
        [FromQuery] int columnId, [FromQuery] double targetFeed,
        [FromQuery] double deltaIAllow = 50, [FromQuery] double sensibleGain = double.NaN,
        [FromQuery] double feedTempRef = double.NaN, [FromQuery] double floodLimit = double.NaN,
        [FromQuery] double n = 1.8, CancellationToken ct = default)
    {
        var a = await _ramp.ComputeAsync(columnId, targetFeed, deltaIAllow, sensibleGain, feedTempRef, floodLimit, n, ct);
        if (a is null) return NotFound(new { error = "config 없음" });
        return Ok(MapRamp(a));
    }

    // ── WP0: Sim Override (DEMO 게이트, 입력 치환 — 제어 쓰기 아님) ──
    private bool SimEnabled() => _appConfig.GetValue<bool>("Feedforward:SimOverrideEnabled");

    [HttpGet("sim/override")]
    public IActionResult GetSimOverride()
    {
        if (!SimEnabled()) return StatusCode(403, new { error = "SimOverride 비활성(Feedforward:SimOverrideEnabled=false)" });
        return Ok(new { enabled = _sim.Enabled, values = _sim.Snapshot() });
    }

    [HttpPost("sim/override")]
    public IActionResult SetSimOverride([FromBody] SimOverrideBody body)
    {
        if (!SimEnabled()) return StatusCode(403, new { error = "SimOverride 비활성" });
        _sim.SetMany(body.enabled, body.values ?? new Dictionary<string, double>());
        return Ok(new { enabled = _sim.Enabled, values = _sim.Snapshot() });
    }

    [HttpDelete("sim/override")]
    public IActionResult ClearSimOverride()
    {
        if (!SimEnabled()) return StatusCode(403, new { error = "SimOverride 비활성" });
        _sim.Clear();
        return Ok(new { enabled = _sim.Enabled });
    }

    // ── WP5 3단계: 조성 분율 수동입력(랩) ──
    [HttpGet("composition")]
    public IActionResult GetComposition() => Ok(new { fractions = _composition.Snapshot() });

    [HttpPost("composition")]
    public IActionResult SetComposition([FromBody] CompositionBody b)
    {
        if (b is null || string.IsNullOrWhiteSpace(b.streamKey)) return BadRequest(new { error = "streamKey 필요" });
        _composition.Set(b.columnId, b.streamKey, b.fraction);
        return Ok(new { fractions = _composition.Snapshot() });
    }

    [HttpDelete("composition/{columnId:int}/{streamKey}")]
    public IActionResult ClearComposition(int columnId, string streamKey)
    { _composition.Clear(columnId, streamKey); return Ok(new { fractions = _composition.Snapshot() }); }

    private static double? Fin(double v) => (double.IsNaN(v) || double.IsInfinity(v)) ? (double?)null : v;
    private static object MapRamp(FeedRampAdvisory a) => new
    {
        columnId = a.ColumnId,
        currentFeed = Fin(a.CurrentFeed), targetFeed = Fin(a.TargetFeed), clampedTarget = Fin(a.ClampedTarget),
        ceiling = new { value = Fin(a.Ceiling.Value), binding = a.Ceiling.Binding },
        rampRate = new { value = Fin(a.RampRate.Value), binding = a.RampRate.Binding },
        rampTimeMin = Fin(a.RampTimeMin),
        steam = new { fiq6115From = a.Steam.Fiq6115From, fiq6115To = a.Steam.Fiq6115To, startOpPct = a.Steam.StartOpPct },
        simOverrideActive = a.SimOverrideActive, hold = a.Hold, warnings = a.Warnings
    };

    // ── Advisory (공개 읽기) ───────────────────────────────────────
    [HttpGet("advisory")]
    public IActionResult GetAll() => Ok(new
    {
        columns = _store.GetAll().Select(r => MapColumn(r))
    });

    [HttpGet("advisory/{columnId:int}")]
    public IActionResult Get(int columnId)
    {
        var r = _store.Get(columnId);
        return r is null ? NotFound() : Ok(MapColumn(r));
    }

    private object MapColumn(AdvisoryResult r)
    {
        var streams = r.Streams.Select(s =>
        {
            var (lastSp, lastErr, lastAt) = _supervisor.GetLastWrite(r.ColumnId, s.Key);
            return new
            {
                key           = s.Key,
                flowTag       = s.FlowTag,
                role          = s.Role.ToString(),
                levelTag      = s.LevelTag,
                pv            = double.IsNaN(s.Pv) ? (double?)null : s.Pv,
                recommendedSp = s.RecommendedSp,
                gap           = s.Gap,
                trend         = s.Trend,
                valid         = s.Valid,
                grade         = s.Grade.ToString(),
                note          = s.Note,
                gradeReason   = s.GradeReason,
                thetaSuggestUpSec = s.ThetaSuggestUpSec,
                thetaSuggestDnSec = s.ThetaSuggestDnSec,
                thetaSuggestConf  = s.ThetaSuggestConf,
                kObsSuggest   = s.KObsSuggest,
                // WP5 2·3단계: 조성 base + trim
                compositionBase = s.CompositionBase,
                trim            = s.Trim,
                recommendedSpComposition = s.RecommendedSpComposition,
                trimSource      = s.TrimSource,
                // Phase II: auto-write 결과
                lastWriteSp    = lastSp,
                lastWriteError = lastErr,
                lastWriteAt    = lastAt
            };
        }).ToList();

        return new
        {
            columnId         = r.ColumnId,
            columnName       = r.ColumnName,
            computedAt       = r.ComputedAt,
            enabled          = r.Enabled,
            transient        = r.Transient,
            transientReason  = r.TransientReason,
            feedFiltered     = r.FeedFiltered,
            vLoss            = r.VLoss,
            yield            = r.Yield,
            massBalanceState = r.MassBalanceState,
            mode             = r.Mode.ToString(),
            modeReason       = r.ModeReason,
            feedRecommendedSp = r.FeedRecommendedSp,
            vLossMa          = r.VLossMa,
            frontPositionState = r.FrontPositionState,
            frontTrimAdvice  = r.FrontTrimAdvice,
            tempProfileState = r.TempProfileState,
            inversionPair    = r.InversionPair,
            tempSpan         = (r.TempSpan is double ts2 && !double.IsNaN(ts2)) ? ts2 : (double?)null,
            tempSpanRef      = r.TempSpanRef,
            upperFrontState  = r.UpperFrontState,
            lowerFrontState  = r.LowerFrontState,
            upperFrontMetric = r.UpperFrontMetric,
            lowerFrontMetric = r.LowerFrontMetric,
            autoWriteActive  = r.AutoWriteActive,
            writeGuardBlockedSp = r.WriteGuardBlockedSp,
            writeGuardReason = r.WriteGuardReason,
            temps            = r.Temps?.Select(t => new
            {
                tag  = t.Tag,
                raw  = double.IsNaN(t.Raw) ? (double?)null : t.Raw,
                pct  = double.IsNaN(t.Pct) ? (double?)null : t.Pct,
                good = t.Good
            }),
            streams          = streams
        };
    }
}

public sealed record WriteSpBody { public double? value { get; init; } }

public sealed record SimOverrideBody
{
    public bool enabled { get; init; }
    public Dictionary<string, double>? values { get; init; }
}

public sealed record CompositionBody
{
    public int columnId { get; init; }
    public string streamKey { get; init; } = "";
    public double fraction { get; init; }
}
