#nullable enable

using System.Data;
using System.Globalization;

namespace MMoneyCoderSharp.Data;

public enum MarketDataView
{
    Kospi,
    Kosdaq,
    Index,
    Overseas
}

public sealed record MarketDataTable(
    string Name,
    DataSourceKind Source,
    IReadOnlyList<string> Columns,
    IReadOnlyList<IReadOnlyList<string?>> Rows,
    int TotalRowCount);

public sealed record MarketDataSnapshot(
    MarketDataView View,
    DateTimeOffset RetrievedAt,
    IReadOnlyList<MarketDataTable> Tables);

public interface IMarketDataService
{
    Task<MarketDataSnapshot> GetAsync(
        MarketDataView view,
        int maximumRowsPerTable = 250,
        CancellationToken cancellationToken = default);
}

/// <summary>
/// Loads the same stock and index lookup queries used by the legacy application.
/// Query text is intentionally kept unchanged during the migration.
/// </summary>
public sealed class LegacyMarketDataService(IDataQueryExecutor executor) : IMarketDataService
{
    private readonly IDataQueryExecutor _executor = executor ?? throw new ArgumentNullException(nameof(executor));

    public async Task<MarketDataSnapshot> GetAsync(
        MarketDataView view,
        int maximumRowsPerTable = 250,
        CancellationToken cancellationToken = default)
    {
        if (maximumRowsPerTable is < 1 or > 2_000)
        {
            throw new ArgumentOutOfRangeException(
                nameof(maximumRowsPerTable),
                "The requested row limit must be between 1 and 2,000.");
        }

        var queries = LegacyMarketQueries.For(view);
        var loads = queries.Select(query => LoadAsync(query, maximumRowsPerTable, cancellationToken));
        var tables = await Task.WhenAll(loads).ConfigureAwait(false);
        return new MarketDataSnapshot(view, DateTimeOffset.Now, tables);
    }

    private async Task<MarketDataTable> LoadAsync(
        LegacyMarketQuery query,
        int maximumRows,
        CancellationToken cancellationToken)
    {
        var table = await _executor
            .ExecuteAsync(query.Source, query.Name, query.Sql, cancellationToken)
            .ConfigureAwait(false);

        var columns = table.Columns.Cast<DataColumn>().Select(column => column.ColumnName).ToArray();
        var rows = table.Rows.Cast<DataRow>()
            .Take(maximumRows)
            .Select(row => (IReadOnlyList<string?>)columns
                .Select(column => FormatValue(row[column]))
                .ToArray())
            .ToArray();

        return new MarketDataTable(query.Name, query.Source, columns, rows, table.Rows.Count);
    }

    private static string? FormatValue(object? value)
    {
        if (value is null or DBNull)
        {
            return null;
        }

        return value switch
        {
            DateTime dateTime => dateTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
            DateTimeOffset dateTimeOffset => dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture),
            IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
            _ => value.ToString()
        };
    }
}

internal sealed record LegacyMarketQuery(string Name, DataSourceKind Source, string Sql);

internal static class LegacyMarketQueries
{
    internal static IReadOnlyList<LegacyMarketQuery> For(MarketDataView view) => view switch
    {
        MarketDataView.Kospi =>
        [
            new("코스피", DataSourceKind.Oracle,
                "SELECT F_STOCK_WANNAME, F_STOCK_CODE FROM T_STOCK WHERE F_MKT_HALT = 'N'"),
            new("NXT코스피", DataSourceKind.MariaDb,
                "SELECT CONCAT(F_STOCK_NAME, '(NXT)') F_STOCK_NAME, F_STOCK_CODE FROM N_STOCK WHERE F_STOP_GUBUN = 'N'")
        ],
        MarketDataView.Kosdaq =>
        [
            new("코스닥", DataSourceKind.Oracle,
                "SELECT F_STOCK_WANNAME, F_STOCK_CODE FROM T_KOSDAQ_STOCK WHERE F_MKT_HALT = 'N'"),
            new("NXT코스닥", DataSourceKind.MariaDb,
                "SELECT CONCAT(F_STOCK_NAME, '(NXT)') F_STOCK_NAME, F_STOCK_CODE FROM N_KOSDAQ_STOCK WHERE F_STOP_GUBUN = 'N'")
        ],
        MarketDataView.Index =>
        [
            new("코스피", DataSourceKind.Oracle, IndexKospi),
            new("코스닥", DataSourceKind.Oracle, IndexKosdaq),
            new("코스피200", DataSourceKind.Oracle, IndexKospi200),
            new("KRX100", DataSourceKind.Oracle, IndexKrx100),
            new("선물", DataSourceKind.Oracle, IndexFuture)
        ],
        MarketDataView.Overseas =>
        [
            new("해외", DataSourceKind.Oracle,
                "SELECT F_KNAM, F_SYMB FROM T_WORLD_IX_EQ_MASTER")
        ],
        _ => throw new ArgumentOutOfRangeException(nameof(view), view, "Unsupported market data view.")
    };

    private const string IndexKospi = @"SELECT '코스피' name, round(f_part_idx/100,2) part_idx, f_chg_type chg_type, round(f_part_chg/100,2) part_chg,
		round((f_part_chg / decode(f_chg_type, '+', f_part_idx - f_part_chg, '-', f_part_idx + f_part_chg, 1)) * 100, 2) rate
		FROM t_index
		WHERE f_part_code = '001'";

    private const string IndexKosdaq = @"SELECT '코스닥' name, round(f_part_idx/100,2) part_idx, f_chg_type chg_type, round(f_part_chg/100,2) part_chg,
		round((f_part_chg / decode(f_chg_type, '+', f_part_idx - f_part_chg, '-', f_part_idx + f_part_chg, 1)) * 100, 2) rate
		FROM t_kosdaq_index
		WHERE f_part_code = '001'";

    private const string IndexKospi200 = @"SELECT '코스피200' name,f_part_idx/100 part_idx, f_chg_type chg_type, f_part_chg/100 part_chg,
		round(f_part_chg / decode(f_chg_type, '+', f_part_idx - f_part_chg, '-', f_part_idx + f_part_chg, 1) * 100, 2) rate
		FROM T_200_INDEX
		WHERE f_part_code = '029'";

    private const string IndexKrx100 = @"SELECT 'KRX100' name, f_part_idx/100 part_idx, f_chg_type chg_type, f_part_chg/100 part_chg,
		round(f_part_chg / decode(f_chg_type, '+', (f_part_idx / 100) - (f_part_chg / 100), '-', (f_part_idx / 100) + (f_part_chg / 100)), 2) rate
		FROM t_krx100_index WHERE f_part_code = '043'";

    private const string IndexFuture = @"SELECT '선물' name, part_idx part_idx, chg_type, ABS(part_chg) part_chg,F_NET_VOL,f_out_vol,  F_NET_TURNOVER,
	ABS(round(part_chg / decode(chg_type, '+', (part_idx / 100) - (part_chg / 100),
		'-', (part_idx / 100) + (part_chg / 100), 1), 2)) rate
	FROM(SELECT decode(a.f_curr_price,0, b.F_JUN_LAST_PRICE/100, a.f_curr_price/100) part_idx, a.F_NET_VOL, a.f_out_vol,  a.F_NET_TURNOVER,
                decode(a.f_curr_price, 0, 0, (a.f_curr_price - b.F_JUN_LAST_PRICE*100)/100) part_chg,
                decode(a.f_curr_price, 0, ' ', decode(sign(a.f_curr_price -b.F_JUN_LAST_PRICE*100),1,'+',-1,'-',0,' ')) chg_type
           FROM t_sunmul_online a, t_sunmul_batch b
          WHERE a.f_stock_code = b.f_stock_code
            AND a.f_stock_seq = 1
            AND b.f_market_date = (select max(OPEN_DAY) from v_open_day)
            AND b.F_MONTH_GUBUN = '1') ";
}
