using System.Text.Json;
using QuantEngine.Core.Interfaces;
using QuantEngine.Core.Models;

namespace QuantEngine.Application.Services;

public class KisApiPriceSource : IPriceSource
{
    private readonly IKisApiClient _kisApiClient;

    public string SourceName => "kis_open_api";

    public KisApiPriceSource(IKisApiClient kisApiClient)
    {
        _kisApiClient = kisApiClient;
    }

    public async Task<PriceSourceResult> GetPriceDataAsync(string ticker, string account)
    {
        try
        {
            var result = new PriceSourceResult { Status = "OK", Source = "kis", Account = account };

            // Get current price
            try
            {
                var price = await _kisApiClient.GetCurrentPriceAsync(ticker, account);
                result.CurrentPrice = CoerceFloat(FindFirstValue(price, "stck_prpr", "stck_clpr", "close"));
                result.Open = CoerceFloat(FindFirstValue(price, "stck_oprc", "open"));
                result.High = CoerceFloat(FindFirstValue(price, "stck_hgpr", "high"));
                result.Low = CoerceFloat(FindFirstValue(price, "stck_lwpr", "low"));
                result.PrevClose = CoerceFloat(FindFirstValue(price, "prdy_vrss"));
                result.Volume = CoerceFloat(FindFirstValue(price, "acml_vol", "volume"));
                result.ChangePct = CoerceFloat(FindFirstValue(price, "prdy_ctrt"));
                result.PriceStatus = "OK";
                result.CurrentPriceRaw = JsonSerializer.Deserialize<Dictionary<string, object>>(JsonSerializer.Serialize(price)) ?? new();
            }
            catch (Exception ex)
            {
                result.PriceStatus = "ERROR";
                result.Error = ex.Message;
            }

            // Get orderbook
            try
            {
                var orderbook = await _kisApiClient.GetAskingPrice10LevelAsync(ticker, account);
                var output1 = ExtractObject(orderbook, "output1");
                result.Ask1 = CoerceFloat(output1.GetValueOrDefault("askp1"));
                result.Bid1 = CoerceFloat(output1.GetValueOrDefault("bidp1"));
                result.OrderbookStatus = "OK";
                result.OrderbookRaw = output1;
            }
            catch (Exception)
            {
                result.OrderbookStatus = "ERROR";
            }

            // Get short sale
            try
            {
                var start = DateTime.Now.AddDays(-10).ToString("yyyyMMdd");
                var end = DateTime.Now.ToString("yyyyMMdd");
                var shortSale = await _kisApiClient.GetDailyShortSaleAsync(ticker, start, end, account);
                var rows = ExtractArray(shortSale, "output2");
                if (rows.Count > 0 && rows[0] is Dictionary<string, object> latest)
                {
                    result.ShortTurnoverShare = CoerceFloat(latest.GetValueOrDefault("ssts_vol_rlim"));
                }
                result.ShortSaleStatus = "OK";
                result.ShortSaleRaw = (Dictionary<string, object>?)rows.FirstOrDefault() ?? new();
            }
            catch (Exception)
            {
                result.ShortSaleStatus = "ERROR";
            }

            return result;
        }
        catch (Exception ex)
        {
            return new PriceSourceResult { Status = "ERROR", Error = ex.Message, Source = "kis", Account = account };
        }
    }

    private static object? FindFirstValue(Dictionary<string, object> payload, params string[] keys)
    {
        var stack = new Stack<object>();
        stack.Push(payload);

        while (stack.Count > 0)
        {
            var item = stack.Pop();
            if (item is Dictionary<string, object> dict)
            {
                foreach (var key in keys)
                {
                    if (dict.TryGetValue(key, out var value) && value != null && !string.IsNullOrEmpty(value.ToString()))
                        return value;
                }
                foreach (var value in dict.Values)
                    if (value != null) stack.Push(value);
            }
            else if (item is JsonElement elem && elem.ValueKind == JsonValueKind.Object)
            {
                foreach (var key in keys)
                {
                    if (elem.TryGetProperty(key, out var prop) && prop.ValueKind != JsonValueKind.Null)
                        return prop;
                }
                foreach (var prop in elem.EnumerateObject())
                    stack.Push(prop.Value);
            }
        }
        return null;
    }

    private static double? CoerceFloat(object? value)
    {
        if (value == null || string.IsNullOrEmpty(value.ToString()))
            return null;
        try
        {
            var str = value.ToString()?.Replace(",", "").Replace("%", "") ?? "";
            return double.TryParse(str, out var d) ? d : null;
        }
        catch { return null; }
    }

    private static Dictionary<string, object> ExtractObject(Dictionary<string, object> payload, string key)
    {
        if (payload.TryGetValue(key, out var value) && value is Dictionary<string, object> dict)
            return dict;
        if (value is JsonElement elem && elem.ValueKind == JsonValueKind.Object)
            return JsonSerializer.Deserialize<Dictionary<string, object>>(elem.GetRawText()) ?? new();
        return new();
    }

    private static List<object> ExtractArray(Dictionary<string, object> payload, string key)
    {
        if (payload.TryGetValue(key, out var value))
        {
            if (value is List<object> list) return list;
            if (value is JsonElement elem && elem.ValueKind == JsonValueKind.Array)
                return JsonSerializer.Deserialize<List<object>>(elem.GetRawText()) ?? new();
        }
        return new();
    }
}
