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

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

public enum LegacyComparisonTargetKind
{
    MarketTarget,
    KrxStock,
    NxtStock,
    WorldStock
}

public enum LegacyComparisonDomesticMarket
{
    Kospi,
    Kosdaq
}

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

public enum LegacyComparisonPairMoveDirection
{
    Up,
    Down
}

/// <summary>
/// Validated native comparison identity. Web code never constructs this type; adapters
/// create it from trusted DB results and the controller only exposes a display projection.
/// </summary>
public sealed record LegacyComparisonTarget
{
    private LegacyComparisonTarget(
        LegacyComparisonTargetKind kind,
        string targetId,
        string displayName,
        string? marketTarget = null,
        LegacyComparisonDomesticMarket? domesticMarket = null,
        string? stockName = null,
        string? stockCode = null,
        string? inputName = null,
        string? symbol = null,
        string? nation = null)
    {
        Kind = kind;
        TargetId = targetId;
        DisplayName = displayName;
        MarketTarget = marketTarget;
        DomesticMarket = domesticMarket;
        StockName = stockName;
        StockCode = stockCode;
        InputName = inputName;
        Symbol = symbol;
        Nation = nation;
    }

    public LegacyComparisonTargetKind Kind { get; }

    public string TargetId { get; }

    public string DisplayName { get; }

    public string? MarketTarget { get; }

    public LegacyComparisonDomesticMarket? DomesticMarket { get; }

    public string? StockName { get; }

    public string? StockCode { get; }

    public string? InputName { get; }

    public string? Symbol { get; }

    public string? Nation { get; }

    public bool CanSelect =>
        Kind != LegacyComparisonTargetKind.WorldStock ||
        !InputName!.Contains(',', StringComparison.Ordinal);

    public string? UnavailableReason => CanSelect
        ? null
        : "쉼표가 포함된 해외 종목명은 비교 대상 구분이 모호해 선택할 수 없습니다.";

    public static LegacyComparisonTarget CreateMarketTarget(
        string targetId,
        string displayName,
        string marketTarget)
    {
        LegacyComparisonValueGuard.RequireIdentifier(targetId, 64, nameof(targetId));
        LegacyComparisonValueGuard.RequireText(displayName, 200, nameof(displayName));
        LegacyComparisonValueGuard.RequireIdentifier(marketTarget, 64, nameof(marketTarget));
        return new LegacyComparisonTarget(
            LegacyComparisonTargetKind.MarketTarget,
            targetId,
            displayName,
            marketTarget: marketTarget);
    }

    public static LegacyComparisonTarget CreateKrxStock(
        LegacyComparisonDomesticMarket market,
        string stockName,
        string stockCode)
    {
        ValidateDomesticStock(stockName, stockCode);
        var marketToken = market == LegacyComparisonDomesticMarket.Kospi ? "kospi" : "kosdaq";
        return new LegacyComparisonTarget(
            LegacyComparisonTargetKind.KrxStock,
            $"krx:{marketToken}:{stockCode}",
            stockName,
            domesticMarket: market,
            stockName: stockName,
            stockCode: stockCode);
    }

    public static LegacyComparisonTarget CreateNxtStock(
        LegacyComparisonDomesticMarket market,
        string stockName,
        string stockCode)
    {
        ValidateDomesticStock(stockName, stockCode);
        var marketToken = market == LegacyComparisonDomesticMarket.Kospi ? "kospi" : "kosdaq";
        return new LegacyComparisonTarget(
            LegacyComparisonTargetKind.NxtStock,
            $"nxt:{marketToken}:{stockCode}",
            $"{stockName}(NXT)",
            domesticMarket: market,
            stockName: stockName,
            stockCode: stockCode);
    }

    public static LegacyComparisonTarget CreateWorldStock(
        string inputName,
        string symbol,
        string nation)
    {
        LegacyComparisonValueGuard.RequireText(inputName, 200, nameof(inputName));
        LegacyComparisonValueGuard.RequireSymbol(symbol, nameof(symbol));
        var normalizedNation = LegacyComparisonValueGuard.RequireNation(nation, nameof(nation));
        return new LegacyComparisonTarget(
            LegacyComparisonTargetKind.WorldStock,
            $"world:{normalizedNation}:{symbol}",
            inputName,
            inputName: inputName,
            symbol: symbol,
            nation: normalizedNation);
    }

    private static void ValidateDomesticStock(string stockName, string stockCode)
    {
        LegacyComparisonValueGuard.RequireText(stockName, 200, nameof(stockName));
        if (stockName.Contains(',', StringComparison.Ordinal) ||
            stockName.Contains("(NXT)", StringComparison.Ordinal))
        {
            throw new ArgumentException(
                "A domestic stock name cannot contain a comma or the NXT display suffix.",
                nameof(stockName));
        }

        LegacyComparisonValueGuard.RequireStockCode(stockCode, nameof(stockCode));
    }
}

public sealed record LegacyComparisonStoredTargetReference(
    string Name,
    string Market);

public interface ILegacyComparisonDataService
{
    Task<IReadOnlyList<LegacyComparisonTarget>> SearchDomesticAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default);

    Task<IReadOnlyList<LegacyComparisonTarget>> SearchWorldAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default);

    Task<LegacyComparisonTarget?> ResolveStoredTargetAsync(
        LegacyComparisonStoredTargetReference target,
        CancellationToken cancellationToken = default);
}

public sealed record LegacyComparisonSearchData(
    IReadOnlyList<LegacyComparisonTarget> Items,
    bool IsTruncated,
    int IsolatedInvalidRowCount)
{
    public int DeferredUnsafeRowCount { get; init; }
}

/// <summary>
/// Optional production telemetry used to preserve safe legacy rows when one provider row
/// cannot become a selectable native identity. Test and offline adapters may continue to
/// implement the list-only compatibility boundary above.
/// </summary>
public interface ILegacyComparisonDataDiagnosticsService
{
    Task<LegacyComparisonSearchData> SearchDomesticWithDiagnosticsAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default);

    Task<LegacyComparisonSearchData> SearchWorldWithDiagnosticsAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default);
}

/// <summary>
/// File IO stays behind this boundary. A production adapter owns the fixed path and CP949
/// encoding; no UI request can provide a path or raw serialized row.
/// </summary>
public interface ILegacyComparisonPairStore
{
    Task<byte[]?> ReadCp949Async(CancellationToken cancellationToken = default);

    Task WriteCp949Async(
        IReadOnlyList<LegacyComparisonPairWriteRow> rows,
        CancellationToken cancellationToken = default);
}

public sealed record LegacyComparisonPairWriteRow(
    int RowNumber,
    string FirstName,
    string FirstMarket,
    string SecondName,
    string SecondMarket);

public sealed record LegacyComparisonTargetView(
    string SelectionId,
    string DisplayName,
    string Metadata,
    LegacyComparisonTargetKind Kind)
{
    public bool CanSelect { get; init; } = true;

    public string? UnavailableReason { get; init; }
}

public sealed record LegacyComparisonSearchSnapshot(
    string Query,
    LegacyComparisonSearchStatus Status,
    IReadOnlyList<LegacyComparisonTargetView> Results,
    string? SelectedResultId,
    int TotalRowCount,
    string ErrorMessage)
{
    public bool IsTruncated { get; init; }

    public int IsolatedInvalidRowCount { get; init; }

    public int DeferredUnsafeRowCount { get; init; }
}

public sealed record LegacyComparisonSavedPairView(
    string RowId,
    int RowNumber,
    string DisplayLabel,
    LegacyComparisonTargetView First,
    LegacyComparisonTargetView Second,
    bool IsSelected);

public sealed record LegacyComparisonImportReceipt(
    string SourceSha256,
    int SourceRowCount,
    int AddedPairCount,
    int SkippedDuplicateCount,
    int TotalPairCount,
    DateTimeOffset ImportedAt);

public sealed record LegacyComparisonActionView(
    string ActionId,
    string Label,
    string Section,
    bool IsAvailable,
    string? UnavailableReason);

public sealed record LegacyComparisonWorkflowSnapshot(
    long Revision,
    bool IsBusy,
    LegacyComparisonSearchSnapshot DomesticSearch,
    LegacyComparisonSearchSnapshot WorldSearch,
    IReadOnlyList<LegacyComparisonTargetView> FixedTargets,
    string? SelectedFixedTargetId,
    LegacyComparisonTargetView? FirstTarget,
    LegacyComparisonTargetView? SecondTarget,
    IReadOnlyList<LegacyComparisonSavedPairView> SavedPairs,
    string? SelectedPairId,
    IReadOnlyList<LegacyComparisonActionView> Actions,
    string StatusMessage,
    LegacyOperatorStatusKind StatusKind)
{
    public bool CanImportLegacyPairs { get; init; }

    public LegacyComparisonImportReceipt? LastImport { get; init; }
}

/// <summary>
/// Generic, already validated playlist materialization. Only an action id crosses the UI
/// command boundary; builder, alias, scene selection and hidden identities are produced here.
/// </summary>
public sealed record LegacyGenericPlaylistDraft(
    string DraftId,
    string BuilderKey,
    string CutCode,
    IReadOnlyList<string> Aliases,
    string Title,
    string Detail,
    string Category,
    string Market,
    LegacySceneSelection Selection,
    bool IsEnabled,
    int FadeDuration,
    string Source,
    string ActionId,
    string? EffectiveMode,
    string? Warning)
{
    public LegacyPlaylistEntry ToLegacyPlaylistEntry() =>
        new(DraftId, CutCode, IsEnabled, FadeDuration, Selection);
}

internal static class LegacyComparisonValueGuard
{
    public static void RequireText(string value, int maximumLength, string parameterName)
    {
        ArgumentNullException.ThrowIfNull(value, parameterName);
        if (value.Length is <= 0 || value.Length > maximumLength ||
            !string.Equals(value, value.Trim(), StringComparison.Ordinal) ||
            value.Contains('^') ||
            value.Any(static character =>
            {
                var category = CharUnicodeInfo.GetUnicodeCategory(character);
                return char.IsControl(character) || char.IsSurrogate(character) ||
                    character == '\uFFFD' ||
                    category is UnicodeCategory.Format or
                        UnicodeCategory.LineSeparator or
                        UnicodeCategory.ParagraphSeparator;
            }))
        {
            throw new ArgumentException("A canonical comparison text value is required.", parameterName);
        }
    }

    public static void RequireIdentifier(string value, int maximumLength, string parameterName)
    {
        ArgumentNullException.ThrowIfNull(value, parameterName);
        if (value.Length is <= 0 || value.Length > maximumLength ||
            value.Any(static character =>
                !char.IsAsciiLetterOrDigit(character) && character is not '-' and not '_'))
        {
            throw new ArgumentException("A safe comparison identifier is required.", parameterName);
        }
    }

    public static void RequireStockCode(string value, string parameterName)
    {
        ArgumentNullException.ThrowIfNull(value, parameterName);
        if (value.Length is <= 0 or > 32 ||
            value.Any(static character =>
                !char.IsAsciiLetterOrDigit(character) && character is not '.' and not '-' and not '_'))
        {
            throw new ArgumentException("A safe stock code is required.", parameterName);
        }
    }

    public static void RequireSymbol(string value, string parameterName)
    {
        if (!LegacyNamedForeignStockRestoreService.IsCanonicalBoundSymbol(value))
        {
            throw new ArgumentException("A safe world symbol is required.", parameterName);
        }
    }

    public static string RequireNation(string value, string parameterName)
    {
        ArgumentNullException.ThrowIfNull(value, parameterName);
        var normalized = value.ToUpperInvariant();
        if (normalized is not ("US" or "TW"))
        {
            throw new ArgumentException("Only the original US and TW universe is supported.", parameterName);
        }

        return normalized;
    }
}
