using System.Globalization;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

public enum LegacyIndustryComparisonSlot
{
    First,
    Second
}

public sealed record LegacyIndustrySelectionSnapshot(
    long Revision,
    IndustryMarket? Market,
    IReadOnlyList<IndustrySelection> Industries,
    IReadOnlyList<LegacyIndustryActionDefinition> Actions,
    int? SelectedIndex,
    IndustrySelection? SelectedIndustry,
    IndustrySelection? FirstIndustry,
    IndustrySelection? SecondIndustry,
    string FirstText,
    string SecondText);

public sealed record LegacyIndustrySceneSelectionDraft(
    string GroupCode,
    string Subject,
    string GraphicType,
    string Subtype,
    string DataCode);

/// <summary>
/// Safe, generic playlist fields produced entirely from the C# catalog and the
/// authoritative UC2 selection state. It intentionally has no caller-supplied row id.
/// </summary>
public sealed record LegacyIndustryPlaylistDraft(
    string ActionId,
    string BuilderKey,
    string SceneAlias,
    int PageSize,
    string Title,
    string Detail,
    string Category,
    IndustryMarket Market,
    LegacyIndustrySceneSelectionDraft Selection,
    string RuntimeAssetRelativePath,
    string RuntimeAssetSha256,
    bool IsEnabled,
    int FadeDuration);

public sealed class LegacyIndustryWorkflowException : Exception
{
    public LegacyIndustryWorkflowException(string message) : base(message)
    {
    }
}

/// <summary>
/// C# authority for the legacy UC2 selector and the UC1 industry actions.
/// </summary>
public sealed class LegacyIndustrySelectionWorkflow
{
    private const int DefaultFadeDuration = 6;
    public const int MaximumComparisonTextLength = 128;

    private static readonly IReadOnlyList<IndustrySelection> NoIndustries =
        Array.AsReadOnly(Array.Empty<IndustrySelection>());

    private static readonly IReadOnlyList<LegacyIndustryActionDefinition> NoActions =
        Array.AsReadOnly(Array.Empty<LegacyIndustryActionDefinition>());

    private readonly IIndustrySelectionService _selectionService;
    private readonly TimeProvider _timeProvider;
    private readonly LegacyIndustryActionLayout _actionLayout;
    private IReadOnlyList<IndustrySelection> _industries = NoIndustries;
    private IReadOnlyList<LegacyIndustryActionDefinition> _actions = NoActions;
    private IndustryMarket? _market;
    private int? _selectedIndex;
    private IndustrySelection? _selectedIndustry;
    private IndustrySelection? _firstIndustry;
    private IndustrySelection? _secondIndustry;
    private string _firstText = string.Empty;
    private string _secondText = string.Empty;
    private long _revision;
    private long _loadGeneration;

    public LegacyIndustrySelectionWorkflow(
        IIndustrySelectionService selectionService,
        TimeProvider? timeProvider = null,
        LegacyIndustryActionLayout? actionLayout = null)
    {
        _selectionService = selectionService ??
            throw new ArgumentNullException(nameof(selectionService));
        _timeProvider = timeProvider ?? TimeProvider.System;
        _actionLayout = actionLayout ?? LegacyIndustryActionLayout.Default;
    }

    public LegacyIndustrySelectionSnapshot Current => CreateSnapshot();

    /// <summary>
    /// Recreates the observable UC2 state for one tab activation. The generation
    /// increment prevents a request started by the discarded control from restoring
    /// its rows after the operator has re-entered the tab.
    /// </summary>
    public LegacyIndustrySelectionSnapshot ResetForTabActivation(IndustryMarket market)
    {
        ValidateMarket(market);
        Interlocked.Increment(ref _loadGeneration);
        _market = market;
        _industries = NoIndustries;
        _actions = _actionLayout.GetActions(market);
        _selectedIndex = null;
        _selectedIndustry = null;
        _firstIndustry = null;
        _secondIndustry = null;
        _firstText = string.Empty;
        _secondText = string.Empty;
        Touch();
        return CreateSnapshot();
    }

    public async Task<LegacyIndustrySelectionSnapshot> LoadMarketAsync(
        IndustryMarket market,
        CancellationToken cancellationToken = default)
    {
        ValidateMarket(market);
        var generation = Interlocked.Increment(ref _loadGeneration);
        var serviceRows = await _selectionService.GetAsync(market, cancellationToken)
            .ConfigureAwait(false);
        cancellationToken.ThrowIfCancellationRequested();

        // A slower previous request must never replace a newer market request.
        if (generation != Volatile.Read(ref _loadGeneration))
        {
            return CreateSnapshot();
        }

        var normalizedRows = NormalizeRows(serviceRows, market);
        if (generation != Volatile.Read(ref _loadGeneration))
        {
            return CreateSnapshot();
        }

        var changingMarket = _market != market;
        var previousSelected = changingMarket ? null : _selectedIndustry;
        var previousFirst = changingMarket ? null : _firstIndustry;
        var previousSecond = changingMarket ? null : _secondIndustry;
        var previousFirstText = changingMarket ? string.Empty : _firstText;
        var previousSecondText = changingMarket ? string.Empty : _secondText;

        _market = market;
        _industries = normalizedRows;
        _actions = _actionLayout.GetActions(market);
        _selectedIndustry = Reconcile(previousSelected, normalizedRows);
        _selectedIndex = _selectedIndustry is null
            ? null
            : FindIdentityIndex(normalizedRows, _selectedIndustry);
        _firstIndustry = Reconcile(previousFirst, normalizedRows);
        _secondIndustry = Reconcile(previousSecond, normalizedRows);
        _firstText = previousFirstText;
        _secondText = previousSecondText;
        Touch();
        return CreateSnapshot();
    }

    public LegacyIndustrySelectionSnapshot SelectIndustry(int index)
    {
        if (!TryGetIndustry(index, out var industry))
        {
            return CreateSnapshot();
        }

        _selectedIndex = index;
        _selectedIndustry = industry;
        Touch();
        return CreateSnapshot();
    }

    public LegacyIndustrySelectionSnapshot DoubleClickIndustry(int index)
    {
        if (!TryGetIndustry(index, out var industry))
        {
            return CreateSnapshot();
        }

        _selectedIndex = index;
        _selectedIndustry = industry;

        // UC2.listbox_DoubleClick copied txt1 to txt2 before assigning the newly
        // double-clicked item to txt1.
        _secondIndustry = _firstIndustry;
        _secondText = _firstText;
        _firstIndustry = industry;
        _firstText = industry.IndustryName;
        Touch();
        return CreateSnapshot();
    }

    public LegacyIndustrySelectionSnapshot SetComparisonText(
        LegacyIndustryComparisonSlot slot,
        string text)
    {
        ArgumentNullException.ThrowIfNull(text);
        if (slot is not (LegacyIndustryComparisonSlot.First or LegacyIndustryComparisonSlot.Second))
        {
            throw new ArgumentOutOfRangeException(nameof(slot), slot, "The comparison slot must be 1 or 2.");
        }

        if (text.Length > MaximumComparisonTextLength ||
            text.Any(static character =>
                char.IsControl(character) || char.IsSurrogate(character) || character == '\uFFFD'))
        {
            throw new LegacyIndustryWorkflowException("The industry comparison text is invalid.");
        }

        if (slot == LegacyIndustryComparisonSlot.First)
        {
            if (string.Equals(_firstText, text, StringComparison.Ordinal))
            {
                return CreateSnapshot();
            }

            _firstText = text;
            _firstIndustry = null;
        }
        else
        {
            if (string.Equals(_secondText, text, StringComparison.Ordinal))
            {
                return CreateSnapshot();
            }

            _secondText = text;
            _secondIndustry = null;
        }

        Touch();
        return CreateSnapshot();
    }

    public LegacyIndustrySelectionSnapshot SwapIndustries()
    {
        if (_firstText.Length == 0 && _secondText.Length == 0)
        {
            return CreateSnapshot();
        }

        (_firstIndustry, _secondIndustry) = (_secondIndustry, _firstIndustry);
        (_firstText, _secondText) = (_secondText, _firstText);
        Touch();
        return CreateSnapshot();
    }

    public LegacyIndustrySelectionSnapshot ClearSelections()
    {
        if (_selectedIndustry is null && _firstText.Length == 0 && _secondText.Length == 0)
        {
            return CreateSnapshot();
        }

        _selectedIndex = null;
        _selectedIndustry = null;
        _firstIndustry = null;
        _secondIndustry = null;
        _firstText = string.Empty;
        _secondText = string.Empty;
        Touch();
        return CreateSnapshot();
    }

    public LegacyIndustryPlaylistDraft MaterializePlaylistDraft(string actionId)
    {
        ArgumentNullException.ThrowIfNull(actionId);
        if (_market is not { } market)
        {
            throw new LegacyIndustryWorkflowException("Select an industry market first.");
        }

        if (!LegacyIndustryActionCatalog.TryGetAction(market, actionId, out var definition) ||
            definition is null)
        {
            throw new LegacyIndustryWorkflowException("The industry action is not registered.");
        }

        var projection = ProjectSelection(definition, market);
        var asset = LegacyIndustryActionCatalog.GetRuntimeAsset(market);
        return new LegacyIndustryPlaylistDraft(
            definition.ActionId,
            definition.BuilderKey,
            definition.SceneAlias,
            definition.PageSize,
            projection.Title,
            definition.Label,
            definition.Category,
            market,
            projection.Selection,
            asset.RelativePath,
            asset.Sha256,
            IsEnabled: true,
            FadeDuration: DefaultFadeDuration);
    }

    private IndustryProjection ProjectSelection(
        LegacyIndustryActionDefinition definition,
        IndustryMarket market)
    {
        var industryGroup = market == IndustryMarket.Kospi
            ? "INDUSTRY_KOSPI"
            : "INDUSTRY_KOSDAQ";
        var marketName = market == IndustryMarket.Kospi ? "KOSPI" : "KOSDAQ";

        return definition.Kind switch
        {
            LegacyIndustryActionKind.Single => ForSelected(
                definition,
                industryGroup,
                "1열판",
                "CURRENT"),
            LegacyIndustryActionKind.Pair => ForPair(definition, industryGroup),
            LegacyIndustryActionKind.Yield => ForSelected(
                definition,
                industryGroup,
                "YIELD_GRAPH",
                definition.YieldPeriod!),
            LegacyIndustryActionKind.Candle => ForSelected(
                definition,
                market == IndustryMarket.Kospi ? "KOSPI_INDUSTRY" : "KOSDAQ_INDUSTRY",
                string.Empty,
                definition.CandleMode == LegacyIndustryCandleMode.Volume ? "VOLUME" : "PRICE"),
            LegacyIndustryActionKind.FiveRow => new IndustryProjection(
                $"{MarketLabel(market)} {definition.Label}",
                new LegacyIndustrySceneSelectionDraft(
                    market == IndustryMarket.Kospi
                        ? "PAGED_DOMESTIC_KOSPI"
                        : "PAGED_DOMESTIC_KOSDAQ",
                    "INDEX",
                    string.Empty,
                    "INDUSTRY",
                    CurrentLocalDate())),
            LegacyIndustryActionKind.Square => new IndustryProjection(
                $"{MarketLabel(market)} {definition.Label}",
                new LegacyIndustrySceneSelectionDraft(
                    industryGroup,
                    marketName,
                    "네모그래프",
                    string.Empty,
                    string.Empty)),
            LegacyIndustryActionKind.Sector => new IndustryProjection(
                $"{MarketLabel(market)} {definition.Label}",
                new LegacyIndustrySceneSelectionDraft(
                    industryGroup,
                    marketName,
                    "SECTOR_INDEX",
                    string.Empty,
                    string.Empty)),
            _ => throw new LegacyIndustryWorkflowException("The industry action kind is unsupported.")
        };
    }

    private IndustryProjection ForSelected(
        LegacyIndustryActionDefinition definition,
        string groupCode,
        string graphicType,
        string subtype)
    {
        if (_selectedIndustry is not { } selected)
        {
            throw new LegacyIndustryWorkflowException(
                $"The {definition.ActionId} action requires one selected industry.");
        }

        return new IndustryProjection(
            selected.IndustryName,
            new LegacyIndustrySceneSelectionDraft(
                groupCode,
                selected.IndustryName,
                graphicType,
                subtype,
                string.Empty));
    }

    private IndustryProjection ForPair(
        LegacyIndustryActionDefinition definition,
        string groupCode)
    {
        if (_selectedIndustry is null ||
            string.IsNullOrWhiteSpace(_firstText) ||
            string.IsNullOrWhiteSpace(_secondText) ||
            _firstText.Contains('-', StringComparison.Ordinal) ||
            _secondText.Contains('-', StringComparison.Ordinal))
        {
            throw new LegacyIndustryWorkflowException(
                $"The {definition.ActionId} action requires a selected industry and two delimiter-safe industries.");
        }

        var subject = $"{_firstText}-{_secondText}";
        return new IndustryProjection(
            subject,
            new LegacyIndustrySceneSelectionDraft(
                groupCode,
                subject,
                "2열판",
                string.Empty,
                string.Empty));
    }

    private string CurrentLocalDate()
    {
        var local = _timeProvider.GetLocalNow();
        return local.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
    }

    private bool TryGetIndustry(int index, out IndustrySelection industry)
    {
        if (index < 0 || index >= _industries.Count)
        {
            industry = null!;
            return false;
        }

        industry = _industries[index];
        return true;
    }

    private static IReadOnlyList<IndustrySelection> NormalizeRows(
        IReadOnlyList<IndustrySelection>? rows,
        IndustryMarket market)
    {
        if (rows is null || rows.Count > LegacyIndustrySelectionService.MaximumResults)
        {
            throw new LegacyIndustryWorkflowException("The industry list is invalid.");
        }

        var normalized = new IndustrySelection[rows.Count];
        for (var index = 0; index < rows.Count; index++)
        {
            var row = rows[index];
            if (row is null || row.Market != market)
            {
                throw new LegacyIndustryWorkflowException(
                    "The industry list contains a cross-market row.");
            }

            var name = row.IndustryName?.Trim();
            var code = row.IndustryCode?.Trim();
            if (string.IsNullOrEmpty(name) ||
                name.Length > LegacyIndustrySelectionService.MaximumNameLength ||
                name.Any(char.IsControl) ||
                string.IsNullOrEmpty(code) ||
                code.Length > 32 ||
                code.Any(character => !char.IsAsciiLetterOrDigit(character)))
            {
                throw new LegacyIndustryWorkflowException(
                    "The industry list contains an unsafe row.");
            }

            normalized[index] = new IndustrySelection(market, name, code);
        }

        return Array.AsReadOnly(normalized);
    }

    private static IndustrySelection? Reconcile(
        IndustrySelection? previous,
        IReadOnlyList<IndustrySelection> rows)
    {
        if (previous is null)
        {
            return null;
        }

        return rows.FirstOrDefault(row => SameIdentity(row, previous));
    }

    private static int? FindIdentityIndex(
        IReadOnlyList<IndustrySelection> rows,
        IndustrySelection selected)
    {
        for (var index = 0; index < rows.Count; index++)
        {
            if (SameIdentity(rows[index], selected))
            {
                return index;
            }
        }

        return null;
    }

    private static bool SameIdentity(IndustrySelection left, IndustrySelection right) =>
        left.Market == right.Market &&
        string.Equals(left.IndustryName, right.IndustryName, StringComparison.Ordinal) &&
        string.Equals(left.IndustryCode, right.IndustryCode, StringComparison.Ordinal);

    private static void ValidateMarket(IndustryMarket market)
    {
        if (market is not (IndustryMarket.Kospi or IndustryMarket.Kosdaq))
        {
            throw new ArgumentOutOfRangeException(
                nameof(market),
                market,
                "Unsupported industry market.");
        }
    }

    private static string MarketLabel(IndustryMarket market) => market switch
    {
        IndustryMarket.Kospi => "코스피",
        IndustryMarket.Kosdaq => "코스닥",
        _ => throw new ArgumentOutOfRangeException(nameof(market), market, null)
    };

    private LegacyIndustrySelectionSnapshot CreateSnapshot() => new(
        _revision,
        _market,
        _industries,
        _actions,
        _selectedIndex,
        _selectedIndustry,
        _firstIndustry,
        _secondIndustry,
        _firstText,
        _secondText);

    private void Touch() => _revision++;

    private sealed record IndustryProjection(
        string Title,
        LegacyIndustrySceneSelectionDraft Selection);
}
