using System.Globalization;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

public enum LegacyTradingHaltRequestStatus
{
    Idle,
    Loading,
    Ready,
    Error
}

public enum LegacyTradingHaltNameSort
{
    None,
    Descending,
    Ascending
}

public sealed record LegacyTradingHaltResultView(
    string SelectionId,
    string Market,
    string StockCode,
    string DisplayName,
    bool IsSelected);

public sealed record LegacyTradingHaltActionView(
    string ActionId,
    string Label,
    bool IsAvailable);

public sealed record LegacyTradingHaltWorkflowSnapshot(
    long Revision,
    string Query,
    LegacyTradingHaltRequestStatus Status,
    IReadOnlyList<LegacyTradingHaltResultView> Results,
    bool IsTruncated,
    LegacyTradingHaltResultView? Selected,
    LegacyTradingHaltNameSort NameSort,
    bool Ma5,
    bool Ma20,
    IReadOnlyList<LegacyTradingHaltActionView> Actions,
    string ErrorMessage,
    string StatusMessage,
    LegacyOperatorStatusKind StatusKind);

public sealed record LegacyTradingHaltPlaylistDraft(
    string DraftId,
    string BuilderKey,
    string CutCode,
    IReadOnlyList<string> Aliases,
    string Title,
    string Detail,
    string Category,
    string Market,
    string StockCode,
    string DisplayName,
    LegacySceneSelection Selection,
    bool Ma5,
    bool Ma20,
    int? CandlePeriodDays,
    string PageText,
    bool IsEnabled,
    int FadeDuration,
    string Source,
    string ActionId)
{
    public LegacyPlaylistEntry ToLegacyPlaylistEntry() =>
        new(DraftId, CutCode, IsEnabled, FadeDuration, Selection);
}

/// <summary>
/// Native authority for UC7's combined KOSPI/KOSDAQ trading-halt selector.
/// The Web selects an opaque row and a closed action id; stock identity and scene fields
/// always come from the validated Core service result.
/// </summary>
public sealed class LegacyTradingHaltWorkflowController
{
    public const string CurrentActionId = "current";
    public const string CandleActionId = "candle-120d";
    public const int CandlePeriodDays = 120;

    private const int DefaultFadeDuration = 6;
    private const string CurrentLabel = "거래정지-현재가";
    private const string CandleLabel = "캔들그래프-거래정지 · 120일";

    private readonly object _gate = new();
    private readonly ITradingHaltSelectionService _service;
    private List<HaltState> _results = [];
    private HaltState? _selected;
    private string _query = string.Empty;
    private string _errorMessage = string.Empty;
    private string _statusMessage = string.Empty;
    private LegacyTradingHaltRequestStatus _status = LegacyTradingHaltRequestStatus.Idle;
    private LegacyTradingHaltNameSort _nameSort;
    private LegacyOperatorStatusKind _statusKind = LegacyOperatorStatusKind.Neutral;
    private bool _isTruncated;
    private bool _ma5;
    private bool _ma20;
    private int _searchGeneration;
    private long _revision;
    private long _draftSequence;

    public LegacyTradingHaltWorkflowController(ITradingHaltSelectionService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public LegacyTradingHaltWorkflowSnapshot Current
    {
        get
        {
            lock (_gate)
            {
                return CreateSnapshot();
            }
        }
    }

    /// <summary>
    /// Recreates UC7's per-control query, result, selection and column-sort state.
    /// MainForm-owned moving-average values remain in force across tab activation.
    /// </summary>
    public LegacyTradingHaltWorkflowSnapshot ResetForTabActivation()
    {
        lock (_gate)
        {
            checked
            {
                _searchGeneration++;
            }

            _results = [];
            _selected = null;
            _query = string.Empty;
            _errorMessage = string.Empty;
            _statusMessage = string.Empty;
            _status = LegacyTradingHaltRequestStatus.Idle;
            _nameSort = LegacyTradingHaltNameSort.None;
            _statusKind = LegacyOperatorStatusKind.Neutral;
            _isTruncated = false;
            Touch();
            return CreateSnapshot();
        }
    }

    public async Task<LegacyTradingHaltWorkflowSnapshot> SearchAsync(
        string query,
        CancellationToken cancellationToken = default)
    {
        var normalizedQuery = NormalizeQuery(query);
        int generation;
        lock (_gate)
        {
            generation = ++_searchGeneration;
            _query = normalizedQuery;
            _results = [];
            _selected = null;
            _nameSort = LegacyTradingHaltNameSort.None;
            _status = LegacyTradingHaltRequestStatus.Loading;
            _isTruncated = false;
            _errorMessage = string.Empty;
            Touch();
        }

        try
        {
            var result = await _service.SearchAsync(
                normalizedQuery,
                LegacyTradingHaltSelectionService.MaximumResults,
                cancellationToken).ConfigureAwait(false);
            lock (_gate)
            {
                if (generation != _searchGeneration)
                {
                    return CreateSnapshot();
                }

                _results = ValidateResult(result, normalizedQuery, generation);
                // UC7 starts with an empty FarPoint sheet. Its first AddRows call
                // makes row zero active, so the operator can press 현재가/120일
                // immediately after a successful search without another click.
                _selected = _results.FirstOrDefault();
                _isTruncated = result.IsTruncated;
                _status = LegacyTradingHaltRequestStatus.Ready;
                _errorMessage = string.Empty;
                _statusMessage = $"KOSPI·KOSDAQ 거래정지 종목 {_results.Count}개를 조회했습니다.";
                _statusKind = LegacyOperatorStatusKind.Information;
                Touch();
                return CreateSnapshot();
            }
        }
        catch (OperationCanceledException)
        {
            lock (_gate)
            {
                if (generation == _searchGeneration)
                {
                    _status = LegacyTradingHaltRequestStatus.Idle;
                    _errorMessage = string.Empty;
                    Touch();
                }
            }

            throw;
        }
        catch
        {
            lock (_gate)
            {
                if (generation == _searchGeneration)
                {
                    _results = [];
                    _selected = null;
                    _status = LegacyTradingHaltRequestStatus.Error;
                    _errorMessage = "거래정지 종목 조회 결과를 안전하게 확인하지 못했습니다.";
                    _statusMessage = _errorMessage;
                    _statusKind = LegacyOperatorStatusKind.Error;
                    Touch();
                }
            }

            throw;
        }
    }

    public LegacyTradingHaltWorkflowSnapshot SelectResult(string selectionId)
    {
        ArgumentNullException.ThrowIfNull(selectionId);
        lock (_gate)
        {
            _selected = _results.FirstOrDefault(item =>
                string.Equals(item.SelectionId, selectionId, StringComparison.Ordinal)) ??
                throw new ArgumentOutOfRangeException(
                    nameof(selectionId),
                    "The trading-halt selection id is not in the current result set.");
            Touch();
            return CreateSnapshot();
        }
    }

    public LegacyTradingHaltWorkflowSnapshot ToggleNameSort()
    {
        lock (_gate)
        {
            _nameSort = _nameSort switch
            {
                LegacyTradingHaltNameSort.Descending => LegacyTradingHaltNameSort.Ascending,
                _ => LegacyTradingHaltNameSort.Descending
            };
            Touch();
            return CreateSnapshot();
        }
    }

    public LegacyTradingHaltWorkflowSnapshot SetMovingAverages(bool ma5, bool ma20)
    {
        lock (_gate)
        {
            _ma5 = ma5;
            _ma20 = ma20;
            Touch();
            return CreateSnapshot();
        }
    }

    public LegacyTradingHaltPlaylistDraft MaterializeSelectedAction(string actionId)
    {
        ArgumentNullException.ThrowIfNull(actionId);
        lock (_gate)
        {
            var selected = _selected ??
                throw new InvalidOperationException("Select a trading-halt stock first.");
            var isCurrent = string.Equals(actionId, CurrentActionId, StringComparison.Ordinal);
            var isCandle = string.Equals(actionId, CandleActionId, StringComparison.Ordinal);
            if (!isCurrent && !isCandle)
            {
                throw new ArgumentOutOfRangeException(nameof(actionId));
            }

            var item = selected.Selection;
            var market = item.Market == TradingHaltMarket.Kospi ? "kospi" : "kosdaq";
            var builderKey = isCurrent ? "s5001" : "s8010";
            var cutCode = isCurrent ? "5001" : "8051";
            var graphicType = isCurrent
                ? "1열판기본"
                : string.Join(
                    ',',
                    new[]
                    {
                        _ma5 ? "MA5" : null,
                        _ma20 ? "MA20" : null
                    }.Where(static value => value is not null));
            var selection = new LegacySceneSelection(
                isCurrent
                    ? item.Market == TradingHaltMarket.Kospi ? "KOSPI" : "KOSDAQ"
                    : item.Market == TradingHaltMarket.Kospi
                        ? "KOSPI_STOCK"
                        : "KOSDAQ_STOCK",
                item.DisplayName,
                graphicType,
                isCurrent ? "HALTED" : "TRADING_HALT",
                item.StockCode);
            return new LegacyTradingHaltPlaylistDraft(
                $"trading-halt-draft-{++_draftSequence:D8}",
                builderKey,
                cutCode,
                Array.AsReadOnly(new[] { cutCode }),
                item.DisplayName,
                isCurrent ? CurrentLabel : CandleLabel,
                isCurrent ? "stock" : "chart",
                market,
                item.StockCode,
                item.DisplayName,
                selection,
                isCandle && _ma5,
                isCandle && _ma20,
                isCandle ? CandlePeriodDays : null,
                "1/1",
                true,
                DefaultFadeDuration,
                "legacy-trading-halt-workflow",
                actionId);
        }
    }

    private LegacyTradingHaltWorkflowSnapshot CreateSnapshot()
    {
        var display = _results.ToArray();
        if (_nameSort != LegacyTradingHaltNameSort.None)
        {
            var multiplier = _nameSort == LegacyTradingHaltNameSort.Ascending ? 1 : -1;
            display = display
                .Select((item, sourceIndex) => (item, sourceIndex))
                .OrderBy(pair => pair.item.Selection.DisplayName, new DirectionComparer(multiplier))
                .ThenBy(pair => pair.item.Selection.Market)
                .ThenBy(pair => pair.item.Selection.StockCode, StringComparer.Ordinal)
                .ThenBy(pair => pair.sourceIndex)
                .Select(pair => pair.item)
                .ToArray();
        }

        var rows = display.Select(ToView).ToArray();
        var selected = _selected is null ? null : ToView(_selected);
        var canAct = _selected is not null;
        return new LegacyTradingHaltWorkflowSnapshot(
            _revision,
            _query,
            _status,
            Array.AsReadOnly(rows),
            _isTruncated,
            selected,
            _nameSort,
            _ma5,
            _ma20,
            Array.AsReadOnly(new[]
            {
                new LegacyTradingHaltActionView(CurrentActionId, CurrentLabel, canAct),
                new LegacyTradingHaltActionView(CandleActionId, CandleLabel, canAct)
            }),
            _errorMessage,
            _statusMessage,
            _statusKind);
    }

    private LegacyTradingHaltResultView ToView(HaltState state) =>
        new(
            state.SelectionId,
            state.Selection.Market == TradingHaltMarket.Kospi ? "kospi" : "kosdaq",
            state.Selection.StockCode,
            state.Selection.DisplayName,
            _selected?.Selection == state.Selection);

    private static List<HaltState> ValidateResult(
        TradingHaltSelectionResult result,
        string expectedQuery,
        int generation)
    {
        ArgumentNullException.ThrowIfNull(result);
        if (!string.Equals(result.Query, expectedQuery, StringComparison.Ordinal) ||
            result.Items is null ||
            result.Items.Count > LegacyTradingHaltSelectionService.MaximumResults ||
            (result.IsTruncated &&
             result.Items.Count != LegacyTradingHaltSelectionService.MaximumResults &&
             !HangulSearchMatcher.IsInitialConsonantQuery(expectedQuery)))
        {
            throw new TradingHaltSelectionDataException(
                "The trading-halt search response correlation is invalid.");
        }

        var states = new List<HaltState>(result.Items.Count);
        var identities = new HashSet<(TradingHaltMarket Market, string Code)>();
        var names = new HashSet<(TradingHaltMarket Market, string Name)>();
        TradingHaltSelection? previous = null;
        for (var index = 0; index < result.Items.Count; index++)
        {
            var item = result.Items[index] ??
                throw new TradingHaltSelectionDataException("A trading-halt row is missing.");
            if (!Enum.IsDefined(item.Market) ||
                !IsAsciiAlphaNumeric(item.StockCode, 32) ||
                !IsCanonicalText(item.DisplayName, 200) ||
                !identities.Add((item.Market, item.StockCode)) ||
                !names.Add((item.Market, item.DisplayName)) ||
                previous is not null && CompareSourceOrder(previous, item) > 0)
            {
                throw new TradingHaltSelectionDataException(
                    "Trading-halt identities must be canonical, unique, and KOSPI/KOSDAQ ordered.");
            }

            states.Add(new HaltState(
                $"trading-halt-result-{generation:D4}-{index + 1:D4}",
                item));
            previous = item;
        }

        return states;
    }

    private static int CompareSourceOrder(
        TradingHaltSelection left,
        TradingHaltSelection right)
    {
        var market = left.Market.CompareTo(right.Market);
        if (market != 0)
        {
            return market;
        }

        var name = string.Compare(left.DisplayName, right.DisplayName, StringComparison.Ordinal);
        return name != 0
            ? name
            : string.Compare(left.StockCode, right.StockCode, StringComparison.Ordinal);
    }

    private static string NormalizeQuery(string query)
    {
        ArgumentNullException.ThrowIfNull(query);
        var normalized = query.Trim();
        if (query.Length > LegacyTradingHaltSelectionService.MaximumQueryLength ||
            normalized.Length > LegacyTradingHaltSelectionService.MaximumQueryLength ||
            !IsSafeText(query) || !IsSafeText(normalized))
        {
            throw new ArgumentException(
                "A trading-halt query must contain at most 64 safe characters.",
                nameof(query));
        }

        return normalized;
    }

    private static bool IsAsciiAlphaNumeric(string? value, int maximumLength) =>
        value is not null && value.Length is > 0 && value.Length <= maximumLength &&
        value.All(static character => char.IsAsciiLetterOrDigit(character));

    private static bool IsCanonicalText(string? value, int maximumLength) =>
        value is not null && value.Length is > 0 && value.Length <= maximumLength &&
        string.Equals(value, value.Trim(), StringComparison.Ordinal) && IsSafeText(value);

    private static bool IsSafeText(string value)
    {
        foreach (var character in value)
        {
            var category = CharUnicodeInfo.GetUnicodeCategory(character);
            if (char.IsControl(character) || char.IsSurrogate(character) ||
                character == '\uFFFD' ||
                category is UnicodeCategory.Format or
                    UnicodeCategory.LineSeparator or
                    UnicodeCategory.ParagraphSeparator)
            {
                return false;
            }
        }

        return true;
    }

    private void Touch() => _revision++;

    private sealed record HaltState(
        string SelectionId,
        TradingHaltSelection Selection);

    private sealed class DirectionComparer(int multiplier) : IComparer<string>
    {
        public int Compare(string? left, string? right) =>
            multiplier * string.Compare(left, right, StringComparison.Ordinal);
    }
}
