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

internal static class ManualFinancialListCompatibilityAudit
{
    internal static async Task RunAsync(
        IDataQueryExecutor executor,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(executor);
        var service = new LegacyManualFinancialScreenService(executor);
        foreach (var contract in ManualFinancialCutContracts.All.OrderBy(
                     contract => contract.Screen))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var table = await executor.ExecuteAsync(
                DataSourceKind.Oracle,
                "GRAPH_E_LIST_COUNT_" + contract.Screen,
                $"SELECT COUNT(*) AS ROW_COUNT FROM {contract.TableName}",
                cancellationToken).ConfigureAwait(false);
            if (table.Columns.Count != 1 || table.Rows.Count != 1 ||
                !string.Equals(
                    table.Columns[0].ColumnName,
                    "ROW_COUNT",
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidDataException(
                    "The GraphE list count query returned an invalid schema.");
            }

            var count = Convert.ToInt32(
                table.Rows[0][0],
                CultureInfo.InvariantCulture);
            if (count < 0)
            {
                throw new InvalidDataException(
                    "The GraphE list count query returned an invalid count.");
            }

            var result = await service.SearchAsync(
                    contract.Screen,
                    query: string.Empty,
                    maximumResults: LegacyManualFinancialScreenService.MaximumResults,
                    cancellationToken: cancellationToken)
                .ConfigureAwait(false);
            var accountedRows = checked(result.Items.Count + result.RejectedItemCount);
            var strictReadyRawRows = result.RawItems.Count(item => item.IsPlayoutReady);
            var rawDeferredRows = result.RawItems.Count(item => !item.IsPlayoutReady);
            if (result.Screen != contract.Screen ||
                result.Query.Length != 0 ||
                result.IsTruncated ||
                !result.RawItemsComplete ||
                accountedRows != count ||
                result.RawItems.Count != count ||
                strictReadyRawRows != result.Items.Count ||
                rawDeferredRows != result.RejectedItemCount ||
                result.UnreadableStockNames.Count != result.RejectedItemCount ||
                result.Items.Any(item =>
                    item.Record.Identity.Screen != contract.Screen) ||
                result.RawItems.Any(item =>
                    item.Record.Identity.Screen != contract.Screen))
            {
                throw new InvalidDataException(
                    "The GraphE full-list compatibility read did not account for every DB row.");
            }

            var rawProbe = result.RawItems.FirstOrDefault(item => !item.IsPlayoutReady) ??
                result.RawItems.FirstOrDefault();
            if (rawProbe is not null)
            {
                var fresh = await service.GetRawAsync(rawProbe, cancellationToken)
                    .ConfigureAwait(false);
                if (fresh.Record.Identity != rawProbe.Record.Identity ||
                    fresh.Record.StorageValues.Count != rawProbe.Record.StorageValues.Count)
                {
                    throw new InvalidDataException(
                        "The GraphE exact raw-row read was not correlated to its list handle.");
                }
            }

            var legacyShapeCompatibleDeferredRows = result.RawItems.Count(item =>
                !item.IsPlayoutReady && IsLegacySceneShapeCompatible(item.Record));

            Console.WriteLine(
                $"GRAPH_E_LIST_COMPAT_AUDIT: screen={contract.Screen} " +
                $"dbRows={count} rawEditable={result.RawItems.Count} " +
                $"strictReady={result.Items.Count} " +
                $"strictDeferred={result.RejectedItemCount} " +
                $"legacyShapeCompatibleDeferred={legacyShapeCompatibleDeferredRows} " +
                $"exactRawReads={(rawProbe is null ? 0 : 1)} truncated=false");
        }
    }

    private static bool IsLegacySceneShapeCompatible(ManualFinancialRawRecord record) =>
        record.Identity.Screen switch
        {
            ManualFinancialScreenKind.RevenueComposition =>
                record.StorageValues.Skip(1).All(IsLegacyRevenueSlice),
            ManualFinancialScreenKind.GrowthMetrics =>
                record.StorageValues.Take(4).All(value => IsLegacyNumberSeries(value, 4)) &&
                HasAtLeastTokens(record.StorageValues[4], 4),
            ManualFinancialScreenKind.Sales or ManualFinancialScreenKind.OperatingProfit =>
                record.StorageValues.All(IsLegacyQuarter),
            _ => false
        };

    private static bool IsLegacyRevenueSlice(string value)
    {
        var tokens = value.Split('_', StringSplitOptions.None);
        if (tokens.Length < 2)
        {
            return value.Length == 0;
        }

        if (tokens[0].Length == 0 || tokens[1] is "" or "-")
        {
            return true;
        }

        return double.TryParse(
                tokens[1],
                NumberStyles.Float | NumberStyles.AllowThousands,
                CultureInfo.CurrentCulture,
                out var parsed) &&
            double.IsFinite(parsed);
    }

    private static bool IsLegacyNumberSeries(string value, int minimumTokens)
    {
        var tokens = value.Split('_', StringSplitOptions.None);
        return tokens.Length >= minimumTokens && tokens.All(token =>
            token.Length == 0 ||
            double.TryParse(
                token,
                NumberStyles.Float | NumberStyles.AllowThousands,
                CultureInfo.CurrentCulture,
                out var parsed) &&
            double.IsFinite(parsed));
    }

    private static bool HasAtLeastTokens(string value, int minimumTokens) =>
        value.Split('_', StringSplitOptions.None).Length >= minimumTokens;

    private static bool IsLegacyQuarter(string value)
    {
        var tokens = value.Split('_', StringSplitOptions.None);
        return tokens.Length >= 2 &&
            (tokens[1].Length == 0 || int.TryParse(
                tokens[1],
                NumberStyles.Integer,
                CultureInfo.CurrentCulture,
                out _));
    }
}
