#nullable enable

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

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

public sealed class S5076SceneDataLoader
{
    private const string Query = """
        SELECT STOCK_NAME, BASE_DATE, GUSUNG_1, GUSUNG_2, GUSUNG_3, GUSUNG_4, GUSUNG_5
        FROM INPUT_PIE
        WHERE STOCK_NAME = :stockName
        """;

    private readonly IDataQueryExecutor _executor;

    public S5076SceneDataLoader(IDataQueryExecutor executor)
    {
        _executor = executor ?? throw new ArgumentNullException(nameof(executor));
    }

    public async Task<S5076SceneData> LoadAsync(
        string stockName,
        CancellationToken cancellationToken = default)
    {
        stockName = LegacySceneBuilderGuard.Text(stockName, nameof(stockName));
        var spec = new DataQuerySpec(
            Query,
            [new DataQueryParameter("stockName", stockName, DbType.String)]);
        var table = await _executor.ExecuteAsync(
            DataSourceKind.Oracle,
            "INPUT_PIE",
            spec,
            cancellationToken).ConfigureAwait(false);
        var row = LegacyManualFinancialStorageCompatibility.SingleEquivalentSceneRow(
            table,
            "STOCK_NAME", "BASE_DATE",
            "GUSUNG_1", "GUSUNG_2", "GUSUNG_3", "GUSUNG_4", "GUSUNG_5");
        var slices = new RevenueSliceData?[5];
        for (var index = 0; index < slices.Length; index++)
        {
            var value = SceneDataTableReader.RequiredString(row, $"GUSUNG_{index + 1}", allowEmpty: true);
            slices[index] = ParseSlice(value);
        }

        return new S5076SceneData(
            SceneDataTableReader.RequiredString(row, "STOCK_NAME"),
            SceneDataTableReader.RequiredString(row, "BASE_DATE", allowEmpty: true),
            slices);
    }

    private static RevenueSliceData? ParseSlice(string value)
    {
        if (value.Length == 0)
        {
            return null;
        }

        var parts = value.Split('_');
        if (parts.Length < 2)
        {
            throw new LegacySceneDataException("INPUT_PIE contains an invalid revenue slice.");
        }

        // Form/GraphE.cs persisted an unused slice as "_" and s5076.cs skipped
        // every slice whose first token was empty. Keep that exact compatibility
        // behavior at the fresh PREPARE read boundary.
        if (parts[0].Length == 0)
        {
            return null;
        }

        var label = parts[0];
        var percentageText = parts[1];
        if (percentageText is "" or "-")
        {
            percentageText = "0";
        }

        if (!double.TryParse(
                percentageText,
                NumberStyles.Float | NumberStyles.AllowThousands,
                CultureInfo.InvariantCulture,
                out var percentage) ||
            !double.IsFinite(percentage))
        {
            throw new LegacySceneDataException("INPUT_PIE contains an invalid revenue percentage.");
        }

        return new RevenueSliceData(label, percentageText, percentage);
    }
}

public sealed class S5081SceneDataLoader
{
    private const string Query = """
        SELECT STOCK_NAME, GUSUNG_1, GUSUNG_2, GUSUNG_3, GUSUNG_4, GUSUNG_5, GUSUNG_6
        FROM INPUT_PROFIT
        WHERE STOCK_NAME = :stockName
        """;

    private readonly IDataQueryExecutor _executor;

    public S5081SceneDataLoader(IDataQueryExecutor executor)
    {
        _executor = executor ?? throw new ArgumentNullException(nameof(executor));
    }

    public async Task<S5081SceneData> LoadAsync(
        string stockName,
        CancellationToken cancellationToken = default)
    {
        stockName = LegacySceneBuilderGuard.Text(stockName, nameof(stockName));
        var spec = new DataQuerySpec(
            Query,
            [new DataQueryParameter("stockName", stockName, DbType.String)]);
        var table = await _executor.ExecuteAsync(
            DataSourceKind.Oracle,
            "INPUT_PROFIT",
            spec,
            cancellationToken).ConfigureAwait(false);
        var row = LegacyManualFinancialStorageCompatibility.SingleEquivalentSceneRow(
            table,
            "STOCK_NAME",
            "GUSUNG_1", "GUSUNG_2", "GUSUNG_3", "GUSUNG_4", "GUSUNG_5", "GUSUNG_6");
        var quarters = new QuarterlyProfitData[6];
        for (var index = 0; index < quarters.Length; index++)
        {
            var value = LegacyManualFinancialStorageCompatibility.RequiredQuarterStorageString(
                row,
                $"GUSUNG_{index + 1}");
            quarters[index] = ParseQuarter(value);
        }

        return new S5081SceneData(
            SceneDataTableReader.RequiredString(row, "STOCK_NAME"),
            quarters);
    }

    private static QuarterlyProfitData ParseQuarter(string value)
    {
        var parts = value.Split('_');
        if (parts.Length < 2)
        {
            throw new LegacySceneDataException("INPUT_PROFIT contains an invalid quarter value.");
        }

        var label = LegacyManualFinancialStorageCompatibility.NormalizeQuarterLabel(parts[0]);
        if (label.Any(char.IsControl))
        {
            throw new LegacySceneDataException("INPUT_PROFIT contains an invalid quarter value.");
        }

        var numberText = parts[1];
        if (numberText.Length == 0)
        {
            numberText = "0";
        }

        if (!int.TryParse(
                numberText,
                NumberStyles.Integer | NumberStyles.AllowThousands,
                CultureInfo.InvariantCulture,
                out var number))
        {
            throw new LegacySceneDataException("INPUT_PROFIT contains an invalid profit value.");
        }

        return new QuarterlyProfitData(label, number);
    }
}

internal static class SceneDataTableReader
{
    public static DataRow SingleRow(DataTable? table, params string[] requiredColumns)
    {
        if (table is null || table.Rows.Count != 1)
        {
            throw new LegacySceneDataException("The scene query must return exactly one row.");
        }

        var available = table.Columns
            .Cast<DataColumn>()
            .Select(column => column.ColumnName)
            .ToHashSet(StringComparer.OrdinalIgnoreCase);
        if (requiredColumns.Any(column => !available.Contains(column)))
        {
            throw new LegacySceneDataException("The scene query returned an unexpected schema.");
        }

        return table.Rows[0];
    }

    public static string RequiredString(
        DataRow row,
        string columnName,
        bool allowEmpty = false)
    {
        var value = row[columnName];
        if (value is null or DBNull)
        {
            if (allowEmpty)
            {
                return string.Empty;
            }

            throw new LegacySceneDataException("The scene query returned a required null value.");
        }

        var text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
        return LegacySceneBuilderGuard.Text(text, columnName, allowEmpty);
    }
}
