using System.Globalization;
using System.Text;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

/// <summary>
/// Adapts the already parameterized domestic/world lookup services to the closed UC3
/// comparison identity model. Database identities never cross the WebView boundary.
/// </summary>
public sealed class LegacyComparisonDataService :
    ILegacyComparisonDataService,
    ILegacyComparisonDataDiagnosticsService
{
    private readonly ILegacyStockLookup _domesticLookup;
    private readonly IWorldStockSearchService _worldLookup;

    public LegacyComparisonDataService(
        ILegacyStockLookup domesticLookup,
        IWorldStockSearchService worldLookup)
    {
        _domesticLookup = domesticLookup ?? throw new ArgumentNullException(nameof(domesticLookup));
        _worldLookup = worldLookup ?? throw new ArgumentNullException(nameof(worldLookup));
    }

    public async Task<IReadOnlyList<LegacyComparisonTarget>> SearchDomesticAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default) =>
        (await SearchDomesticWithDiagnosticsAsync(
            query,
            maximumResults,
            cancellationToken).ConfigureAwait(false)).Items;

    public async Task<LegacyComparisonSearchData> SearchDomesticWithDiagnosticsAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default)
    {
        ValidateLimit(maximumResults, LegacyComparisonWorkflowCatalog.DomesticMaximumResults);
        var result = await _domesticLookup.SearchAsync(query, cancellationToken)
            .ConfigureAwait(false);
        var targets = new List<LegacyComparisonTarget>(
            Math.Min(result.DisplayNames.Count, maximumResults));
        var identities = new HashSet<string>(StringComparer.Ordinal);
        var isolatedInvalidRowCount = 0;
        var isLimitTruncated = false;
        foreach (var displayName in result.DisplayNames)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var identity = result.ResolveLastByDisplayName(displayName);
            if (identity is null)
            {
                isolatedInvalidRowCount++;
                continue;
            }

            LegacyComparisonTarget target;
            try
            {
                target = ToComparisonTarget(identity);
            }
            catch (ArgumentException)
            {
                isolatedInvalidRowCount++;
                continue;
            }
            if (identities.Add(target.TargetId))
            {
                if (targets.Count < maximumResults)
                {
                    targets.Add(target);
                }
                else
                {
                    isLimitTruncated = true;
                }
            }
        }

        return new LegacyComparisonSearchData(
            targets.AsReadOnly(),
            isLimitTruncated || isolatedInvalidRowCount > 0,
            isolatedInvalidRowCount)
        {
            DeferredUnsafeRowCount = targets.Count(target => !target.CanSelect)
        };
    }

    public async Task<IReadOnlyList<LegacyComparisonTarget>> SearchWorldAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default) =>
        (await SearchWorldWithDiagnosticsAsync(
            query,
            maximumResults,
            cancellationToken).ConfigureAwait(false)).Items;

    public async Task<LegacyComparisonSearchData> SearchWorldWithDiagnosticsAsync(
        string query,
        int maximumResults,
        CancellationToken cancellationToken = default)
    {
        ValidateLimit(maximumResults, LegacyComparisonWorkflowCatalog.WorldMaximumResults);
        var result = await _worldLookup.SearchAsync(query, maximumResults, cancellationToken)
            .ConfigureAwait(false);
        var targets = new List<LegacyComparisonTarget>(result.Items.Count);
        var isolatedInvalidRowCount = result.IsolatedInvalidRowCount;
        foreach (var item in result.Items)
        {
            try
            {
                var target = LegacyComparisonTarget.CreateWorldStock(
                    item.InputName,
                    item.Symbol,
                    item.NationCode);
                targets.Add(target);
            }
            catch (ArgumentException)
            {
                isolatedInvalidRowCount++;
            }
        }

        return new LegacyComparisonSearchData(
            targets.AsReadOnly(),
            result.IsTruncated || isolatedInvalidRowCount > 0,
            isolatedInvalidRowCount)
        {
            DeferredUnsafeRowCount = targets.Count(target => !target.CanSelect)
        };
    }

    public async Task<LegacyComparisonTarget?> ResolveStoredTargetAsync(
        LegacyComparisonStoredTargetReference target,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(target);
        if (target.Market.Length == 0)
        {
            var world = await SearchWorldAsync(
                target.Name,
                LegacyComparisonWorkflowCatalog.WorldMaximumResults,
                cancellationToken).ConfigureAwait(false);
            return world.FirstOrDefault(item =>
                LegacyComparisonWorkflowCatalog.MatchesStoredReference(item, target));
        }

        var domestic = await SearchDomesticAsync(
            RemoveNxtSuffix(target.Name),
            LegacyComparisonWorkflowCatalog.DomesticMaximumResults,
            cancellationToken).ConfigureAwait(false);
        return domestic.FirstOrDefault(item =>
            LegacyComparisonWorkflowCatalog.MatchesStoredReference(item, target));
    }

    private static LegacyComparisonTarget ToComparisonTarget(LegacyStockIdentity identity)
    {
        var name = RemoveNxtSuffix(identity.DisplayName);
        return identity.Market switch
        {
            LegacyDomesticStockMarket.Kospi => LegacyComparisonTarget.CreateKrxStock(
                LegacyComparisonDomesticMarket.Kospi,
                name,
                identity.StockCode),
            LegacyDomesticStockMarket.Kosdaq => LegacyComparisonTarget.CreateKrxStock(
                LegacyComparisonDomesticMarket.Kosdaq,
                name,
                identity.StockCode),
            LegacyDomesticStockMarket.NxtKospi => LegacyComparisonTarget.CreateNxtStock(
                LegacyComparisonDomesticMarket.Kospi,
                name,
                identity.StockCode),
            LegacyDomesticStockMarket.NxtKosdaq => LegacyComparisonTarget.CreateNxtStock(
                LegacyComparisonDomesticMarket.Kosdaq,
                name,
                identity.StockCode),
            _ => throw new InvalidOperationException("The domestic stock market is invalid.")
        };
    }

    private static string RemoveNxtSuffix(string value) =>
        value.EndsWith("(NXT)", StringComparison.Ordinal)
            ? value[..^5]
            : value;

    private static void ValidateLimit(int value, int maximum)
    {
        if (value is < 1 || value > maximum)
        {
            throw new ArgumentOutOfRangeException(nameof(value));
        }
    }
}

/// <summary>
/// Trusted, fixed-path CP949 store for the original 종목비교.dat shape. Reads may fall
/// back to a read-only seed, but all writes are atomic and confined to the local path.
/// </summary>
public sealed class LegacyComparisonPairFileStore : ILegacyComparisonPairStore
{
    private readonly string _path;
    private readonly string? _readOnlySeedPath;

    public LegacyComparisonPairFileStore(string path, string? readOnlySeedPath = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(path);
        _path = Path.GetFullPath(path);
        _readOnlySeedPath = string.IsNullOrWhiteSpace(readOnlySeedPath)
            ? null
            : Path.GetFullPath(readOnlySeedPath);
    }

    public async Task<byte[]?> ReadCp949Async(CancellationToken cancellationToken = default)
    {
        var source = File.Exists(_path)
            ? _path
            : _readOnlySeedPath is not null && File.Exists(_readOnlySeedPath)
                ? _readOnlySeedPath
                : null;
        return source is null
            ? null
            : await File.ReadAllBytesAsync(source, cancellationToken).ConfigureAwait(false);
    }

    public async Task WriteCp949Async(
        IReadOnlyList<LegacyComparisonPairWriteRow> rows,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(rows);
        if (rows.Count > MMoneyCoderSharp.Data.LegacyComparisonFileParser.MaximumRows)
        {
            throw new ArgumentOutOfRangeException(nameof(rows));
        }

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        var cp949 = Encoding.GetEncoding(
            949,
            EncoderFallback.ExceptionFallback,
            DecoderFallback.ExceptionFallback);
        var text = string.Join(
            '\n',
            rows.Select((row, index) => SerializeRow(row, index + 1)));
        var bytes = cp949.GetBytes(text);
        if (bytes.Length > 0)
        {
            _ = MMoneyCoderSharp.Data.LegacyComparisonFileParser.Parse(bytes);
        }

        var directory = Path.GetDirectoryName(_path) ??
            throw new InvalidOperationException("The comparison data path has no directory.");
        Directory.CreateDirectory(directory);
        var temporaryPath = Path.Combine(
            directory,
            $".{Path.GetFileName(_path)}.{Guid.NewGuid():N}.tmp");
        try
        {
            await File.WriteAllBytesAsync(temporaryPath, bytes, cancellationToken)
                .ConfigureAwait(false);
            File.Move(temporaryPath, _path, overwrite: true);
        }
        finally
        {
            if (File.Exists(temporaryPath))
            {
                File.Delete(temporaryPath);
            }
        }
    }

    private static string SerializeRow(LegacyComparisonPairWriteRow row, int expectedNumber)
    {
        ArgumentNullException.ThrowIfNull(row);
        if (row.RowNumber != expectedNumber)
        {
            throw new InvalidOperationException("Comparison row numbers must be contiguous.");
        }

        return string.Join(
            '^',
            row.RowNumber.ToString(CultureInfo.InvariantCulture),
            $"{row.FirstName},{row.SecondName}",
            row.FirstMarket,
            row.SecondMarket,
            string.Empty,
            string.Empty,
            string.Empty,
            string.Empty,
            string.Empty);
    }
}
