using System.Collections.ObjectModel;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

internal enum LegacyComparisonActionKind
{
    TwoColumn,
    Comparison,
    Return
}

internal sealed record LegacyComparisonActionDefinition(
    string ActionId,
    string Label,
    string Section,
    LegacyComparisonActionKind Kind,
    string? Mode = null,
    string? BuilderKey = null,
    string? CutCode = null,
    string? GraphicType = null,
    string? Period = null);

internal sealed record LegacyComparisonCompatibility(
    bool IsAvailable,
    string? Reason,
    string? EffectiveMode,
    string? Warning);

internal static class LegacyComparisonWorkflowCatalog
{
    public const int DefaultFadeDuration = 6;
    public const int DomesticMaximumResults = LegacyStockSearchService.MaximumResults;
    public const int WorldMaximumResults = LegacyWorldStockSearchService.MaximumResults;

    private static readonly ReadOnlyCollection<LegacyComparisonTarget> FixedTargetList =
        Array.AsReadOnly(new[]
        {
            Market("kospi", "코스피 지수", "Kospi"),
            Market("kosdaq", "코스닥 지수", "Kosdaq"),
            Market("kospi200", "코스피200 지수", "Kospi200"),
            Market("futures", "선물 지수", "Futures"),
            Market("krx100", "KRX100지수", "Krx100"),
            Market("won-dollar", "환율-원달러", "WonDollar"),
            Market("won-yen", "환율-원엔", "WonYen"),
            Market("won-yuan", "환율-원위엔", "WonYuan"),
            Market("won-euro", "환율-원유로", "WonEuro"),
            Market("dow", "해외지수-다우", "Dow"),
            Market("nasdaq", "해외지수-나스닥", "Nasdaq"),
            Market("sp500", "해외지수-S&P", "Sp500"),
            Market("germany-dax", "해외지수-독일", "GermanyDax"),
            Market("united-kingdom-ftse", "해외지수-영국", "UnitedKingdomFtse"),
            Market("france-cac", "해외지수-프랑스", "FranceCac"),
            Market("nikkei", "해외지수-일본", "Nikkei"),
            Market("hang-seng", "해외지수-홍콩", "HangSeng"),
            Market("taiwan-weighted", "해외지수-대만", "TaiwanWeighted"),
            Market("singapore-straits-times", "해외지수-싱가포르", "SingaporeStraitsTimes"),
            Market("thailand-set", "해외지수-태국", "ThailandSet"),
            Market("philippines-composite", "해외지수-필리핀", "PhilippinesComposite"),
            Market("malaysia-klse", "해외지수-말레이시아", "MalaysiaKlse"),
            Market("indonesia-composite", "해외지수-인도네시아", "IndonesiaComposite"),
            Market("shanghai-composite", "해외지수-중국", "ShanghaiComposite")
        });

    private static readonly IReadOnlyDictionary<string, LegacyComparisonTarget> FixedById =
        FixedTargetList.ToDictionary(static target => target.TargetId, StringComparer.Ordinal);

    private static readonly IReadOnlyDictionary<string, LegacyComparisonTarget> FixedByDisplayName =
        FixedTargetList.ToDictionary(static target => target.DisplayName, StringComparer.Ordinal);

    private static readonly ReadOnlyCollection<LegacyComparisonActionDefinition> ActionList =
        Array.AsReadOnly(new[]
        {
            new LegacyComparisonActionDefinition(
                "two-column-expected", "2열판 예상체결", "2열판",
                LegacyComparisonActionKind.TwoColumn, Mode: "EXPECTED"),
            new LegacyComparisonActionDefinition(
                "two-column-current", "2열판", "2열판",
                LegacyComparisonActionKind.TwoColumn, Mode: "CURRENT"),
            new LegacyComparisonActionDefinition(
                "comparison-candle", "종목별 비교분석_캔들 그래프", "종목별 비교분석",
                LegacyComparisonActionKind.Comparison,
                BuilderKey: "s5026", CutCode: "5026",
                GraphicType: "COMPARISON_CANDLE", Period: "FiveDays"),
            new LegacyComparisonActionDefinition(
                "comparison-line", "종목별 비교분석_라인 그래프", "종목별 비교분석",
                LegacyComparisonActionKind.Comparison,
                BuilderKey: "s5087", CutCode: "5087",
                GraphicType: "COMPARISON_LINE", Period: "FiveDays"),
            Return("return-5d", "5일", "FiveDays"),
            Return("return-1m", "1개월", "OneMonth"),
            Return("return-3m", "3개월", "ThreeMonths"),
            Return("return-6m", "6개월", "SixMonths"),
            Return("return-12m", "12개월", "TwelveMonths")
        });

    private static readonly IReadOnlyDictionary<string, LegacyComparisonActionDefinition> ActionsById =
        ActionList.ToDictionary(static action => action.ActionId, StringComparer.Ordinal);

    private static readonly HashSet<string> ExpectedMarketTargets =
        new(StringComparer.Ordinal) { "Kospi", "Kosdaq", "Kospi200", "Krx100" };

    static LegacyComparisonWorkflowCatalog()
    {
        if (FixedTargetList.Count != 24 || FixedById.Count != 24 ||
            ActionList.Count != 9 || ActionsById.Count != 9)
        {
            throw new InvalidOperationException("The closed UC3 comparison catalog is invalid.");
        }
    }

    public static IReadOnlyList<LegacyComparisonTarget> FixedTargets => FixedTargetList;

    internal static IReadOnlyList<LegacyComparisonActionDefinition> Actions => ActionList;

    public static LegacyComparisonTarget? GetFixedTarget(string targetId) =>
        targetId is not null && FixedById.TryGetValue(targetId, out var target) ? target : null;

    public static LegacyComparisonTarget RequireCanonicalTarget(LegacyComparisonTarget target)
    {
        ArgumentNullException.ThrowIfNull(target);
        if (target.Kind != LegacyComparisonTargetKind.MarketTarget)
        {
            return target;
        }

        if (!FixedById.TryGetValue(target.TargetId, out var canonical) || canonical != target)
        {
            throw new InvalidOperationException("The data service returned an unknown market target.");
        }

        return canonical;
    }

    public static LegacyComparisonTargetView ToView(
        LegacyComparisonTarget target,
        string selectionId)
    {
        ArgumentNullException.ThrowIfNull(target);
        LegacyComparisonValueGuard.RequireIdentifier(selectionId, 128, nameof(selectionId));
        var metadata = target.Kind switch
        {
            LegacyComparisonTargetKind.MarketTarget => target.MarketTarget!,
            LegacyComparisonTargetKind.WorldStock => $"{target.Nation} · {target.Symbol}",
            LegacyComparisonTargetKind.KrxStock =>
                $"{MarketText(target.DomesticMarket)} KRX · {target.StockCode}",
            LegacyComparisonTargetKind.NxtStock =>
                $"{MarketText(target.DomesticMarket)} NXT · {target.StockCode}",
            _ => throw new InvalidOperationException("The comparison target kind is invalid.")
        };
        return new LegacyComparisonTargetView(
            selectionId,
            target.DisplayName,
            metadata,
            target.Kind)
        {
            CanSelect = target.CanSelect,
            UnavailableReason = target.UnavailableReason
        };
    }

    public static LegacyComparisonStoredTargetReference ToStoredReference(
        LegacyComparisonTarget target)
    {
        ArgumentNullException.ThrowIfNull(target);
        if (!target.CanSelect)
        {
            throw new InvalidOperationException(
                "A deferred comparison row cannot be serialized.");
        }

        return target.Kind switch
        {
            LegacyComparisonTargetKind.MarketTarget =>
                new LegacyComparisonStoredTargetReference(target.DisplayName, string.Empty),
            LegacyComparisonTargetKind.KrxStock =>
                new LegacyComparisonStoredTargetReference(
                    target.StockName!,
                    target.DomesticMarket == LegacyComparisonDomesticMarket.Kospi
                        ? "코스피"
                        : "코스닥"),
            LegacyComparisonTargetKind.NxtStock =>
                new LegacyComparisonStoredTargetReference(
                    target.DisplayName,
                    target.DomesticMarket == LegacyComparisonDomesticMarket.Kospi
                        ? "코스피_NXT"
                        : "코스닥_NXT"),
            LegacyComparisonTargetKind.WorldStock =>
                new LegacyComparisonStoredTargetReference(target.InputName!, string.Empty),
            _ => throw new InvalidOperationException("The comparison target kind is invalid.")
        };
    }

    public static bool MatchesStoredReference(
        LegacyComparisonTarget target,
        LegacyComparisonStoredTargetReference stored) =>
        target.CanSelect && ToStoredReference(target) == stored;

    public static LegacyComparisonTarget? TryResolveFixedStoredTarget(
        LegacyComparisonStoredTargetReference stored) =>
        stored.Market.Length == 0 &&
        FixedByDisplayName.TryGetValue(stored.Name, out var target)
            ? target
            : null;

    public static IReadOnlyList<LegacyComparisonActionView> CreateActionViews(
        LegacyComparisonTarget? first,
        LegacyComparisonTarget? second)
    {
        var placements = ActionList
            .Select(action => new LegacyComparisonActionPlacement(action.ActionId, action.Section))
            .ToArray();
        return CreateActionViews(first, second, placements);
    }

    internal static IReadOnlyList<LegacyComparisonActionView> CreateActionViews(
        LegacyComparisonTarget? first,
        LegacyComparisonTarget? second,
        IReadOnlyList<LegacyComparisonActionPlacement> placements)
    {
        ArgumentNullException.ThrowIfNull(placements);
        var views = placements.Select(placement =>
        {
            if (!ActionsById.TryGetValue(placement.ActionId, out var action))
            {
                throw new InvalidOperationException(
                    "The runtime comparison layout contains an unknown action.");
            }

            var compatibility = GetCompatibility(action, first, second);
            return new LegacyComparisonActionView(
                action.ActionId,
                action.Label,
                placement.Section,
                compatibility.IsAvailable,
                compatibility.Reason);
        }).ToArray();
        return Array.AsReadOnly(views);
    }

    public static LegacyGenericPlaylistDraft Materialize(
        string actionId,
        LegacyComparisonTarget first,
        LegacyComparisonTarget second,
        string draftId)
    {
        ArgumentNullException.ThrowIfNull(actionId);
        if (!ActionsById.TryGetValue(actionId, out var action))
        {
            throw new ArgumentOutOfRangeException(nameof(actionId), "The comparison action is unknown.");
        }

        var compatibility = GetCompatibility(action, first, second);
        if (!compatibility.IsAvailable)
        {
            throw new InvalidOperationException(
                $"The selected pair cannot use this comparison action: {compatibility.Reason}.");
        }

        LegacyComparisonValueGuard.RequireIdentifier(draftId, 128, nameof(draftId));
        var subject = $"{SubjectToken(first)},{SubjectToken(second)}";
        string builderKey;
        string cutCode;
        string graphicType;
        string subtype;
        string dataCode;
        string category;
        if (action.Kind == LegacyComparisonActionKind.TwoColumn)
        {
            var hasFutures = IsFutures(first) || IsFutures(second);
            builderKey = hasFutures ? "s5032" : "s8018";
            cutCode = hasFutures ? "5032" : "8032";
            graphicType = "2열판";
            subtype = action.Mode!;
            dataCode = string.Empty;
            category = "plate";
        }
        else
        {
            builderKey = action.Kind == LegacyComparisonActionKind.Return
                ? "s5029"
                : action.BuilderKey!;
            cutCode = action.Kind == LegacyComparisonActionKind.Return
                ? "5029"
                : action.CutCode!;
            graphicType = action.Kind == LegacyComparisonActionKind.Return
                ? "RETURN_COMPARISON"
                : action.GraphicType!;
            subtype = action.Period!;
            dataCode = ExactKrxIdentityEnvelope(first, second);
            category = "chart";
        }

        var selection = new LegacySceneSelection(
            first.Kind == LegacyComparisonTargetKind.MarketTarget ||
            second.Kind == LegacyComparisonTargetKind.MarketTarget
                ? "INDEX"
                : "STOCK",
            subject,
            graphicType,
            subtype,
            dataCode);
        return new LegacyGenericPlaylistDraft(
            draftId,
            builderKey,
            cutCode,
            Array.AsReadOnly(new[] { cutCode }),
            $"{first.DisplayName} ↔ {second.DisplayName}",
            action.Label,
            category,
            "compare",
            selection,
            true,
            DefaultFadeDuration,
            "legacy-comparison-workflow",
            action.ActionId,
            compatibility.EffectiveMode,
            compatibility.Warning);
    }

    public static string PairSignature(
        LegacyComparisonTarget first,
        LegacyComparisonTarget second)
    {
        var firstStored = ToStoredReference(first);
        var secondStored = ToStoredReference(second);
        return string.Join(
            '\u001f',
            firstStored.Name,
            firstStored.Market,
            secondStored.Name,
            secondStored.Market);
    }

    private static LegacyComparisonCompatibility GetCompatibility(
        LegacyComparisonActionDefinition action,
        LegacyComparisonTarget? first,
        LegacyComparisonTarget? second)
    {
        if (first is null || second is null)
        {
            return Unavailable("pair-incomplete-or-invalid");
        }

        if (!first.CanSelect || !second.CanSelect)
        {
            return Unavailable("deferred-unsafe-comparison-row");
        }

        if (action.Kind is LegacyComparisonActionKind.Comparison or LegacyComparisonActionKind.Return)
        {
            return first.Kind == LegacyComparisonTargetKind.KrxStock &&
                   second.Kind == LegacyComparisonTargetKind.KrxStock
                ? Available("GRAPH")
                : Unavailable("comparison-graphs-require-two-krx-stocks");
        }

        var futuresCount = (IsFutures(first) ? 1 : 0) + (IsFutures(second) ? 1 : 0);
        if (futuresCount > 1)
        {
            return Unavailable("two-futures-targets-are-unsupported");
        }

        if (futuresCount == 1)
        {
            var counterpart = IsFutures(first) ? second : first;
            if (counterpart.Kind != LegacyComparisonTargetKind.MarketTarget)
            {
                return Unavailable("futures-requires-a-market-target-counterpart");
            }

            return action.Mode == "EXPECTED"
                ? Available(
                    "CURRENT",
                    "The current Core routes a futures expected action through the current s5032 branch.")
                : Available("CURRENT");
        }

        if (action.Mode == "EXPECTED")
        {
            var supported = IsExpectedTarget(first) && IsExpectedTarget(second);
            return supported
                ? Available("EXPECTED")
                : Unavailable(
                    "expected-mode-supports-only-krx-stocks-and-four-domestic-indices");
        }

        var hasMarket = first.Kind == LegacyComparisonTargetKind.MarketTarget ||
                        second.Kind == LegacyComparisonTargetKind.MarketTarget;
        var hasWorld = first.Kind == LegacyComparisonTargetKind.WorldStock ||
                       second.Kind == LegacyComparisonTargetKind.WorldStock;
        return hasMarket && hasWorld
            ? Unavailable("mixed-market-and-world-is-unsupported")
            : Available("CURRENT");
    }

    private static bool IsExpectedTarget(LegacyComparisonTarget target) =>
        target.Kind == LegacyComparisonTargetKind.KrxStock ||
        target.Kind == LegacyComparisonTargetKind.MarketTarget &&
        ExpectedMarketTargets.Contains(target.MarketTarget!);

    private static bool IsFutures(LegacyComparisonTarget target) =>
        target.Kind == LegacyComparisonTargetKind.MarketTarget &&
        string.Equals(target.MarketTarget, "Futures", StringComparison.Ordinal);

    private static string SubjectToken(LegacyComparisonTarget target) => target.Kind switch
    {
        LegacyComparisonTargetKind.MarketTarget => target.MarketTarget!,
        LegacyComparisonTargetKind.KrxStock => target.StockName!,
        LegacyComparisonTargetKind.NxtStock => target.DisplayName,
        LegacyComparisonTargetKind.WorldStock => target.InputName!,
        _ => throw new InvalidOperationException("The comparison target kind is invalid.")
    };

    private static string ExactKrxIdentityEnvelope(
        LegacyComparisonTarget first,
        LegacyComparisonTarget second)
    {
        if (first.Kind != LegacyComparisonTargetKind.KrxStock ||
            second.Kind != LegacyComparisonTargetKind.KrxStock)
        {
            throw new InvalidOperationException("Comparison graphs require two KRX stocks.");
        }

        return $"{MarketIdentity(first)}:{first.StockCode}|{MarketIdentity(second)}:{second.StockCode}";
    }

    private static string MarketIdentity(LegacyComparisonTarget target) =>
        target.DomesticMarket == LegacyComparisonDomesticMarket.Kospi ? "KOSPI" : "KOSDAQ";

    private static string MarketText(LegacyComparisonDomesticMarket? market) => market switch
    {
        LegacyComparisonDomesticMarket.Kospi => "KOSPI",
        LegacyComparisonDomesticMarket.Kosdaq => "KOSDAQ",
        _ => throw new InvalidOperationException("The domestic comparison market is missing.")
    };

    private static LegacyComparisonCompatibility Available(
        string effectiveMode,
        string? warning = null) =>
        new(true, null, effectiveMode, warning);

    private static LegacyComparisonCompatibility Unavailable(string reason) =>
        new(false, reason, null, null);

    private static LegacyComparisonTarget Market(
        string id,
        string displayName,
        string target) =>
        LegacyComparisonTarget.CreateMarketTarget(id, displayName, target);

    private static LegacyComparisonActionDefinition Return(
        string id,
        string label,
        string period) =>
        new(
            id,
            label,
            "종목별 수익률 비교",
            LegacyComparisonActionKind.Return,
            Period: period);
}
