using System.Net;
using System.Text.Json;
using KArtSell.BuildingBlocks.Time;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace KArtSell.Modules.ModelOperations.ShadowRun.Services;

/// <summary>
/// Integrates with Korea Investment & Securities (KIS) API for trading & portfolio management.
/// Implements connection pooling, token refresh, and order execution.
/// </summary>
public sealed class KisDataService : IKisDataService
{
    private readonly HttpClient _httpClient;
    private readonly IMemoryCache _cache;
    private readonly IClock _clock;
    private readonly ILogger<KisDataService> _logger;

    private const int CacheDurationMinutes = 60;  // 1 hour for positions
    private const int MaxRetries = 3;
    private const int InitialBackoffMs = 300;
    private const int MaxBackoffMs = 90000;
    private const string KisApiBaseUrl = "https://openapivts.kish.com";

    private static readonly Action<ILogger, string, Exception?> LogFetchingOrders =
        LoggerMessage.Define<string>(
            LogLevel.Information,
            new EventId(20, nameof(LogFetchingOrders)),
            "Fetching KIS trading orders for {AccountNumber}");

    private static readonly Action<ILogger, string, int, Exception?> LogFetchedOrders =
        LoggerMessage.Define<string, int>(
            LogLevel.Information,
            new EventId(21, nameof(LogFetchedOrders)),
            "Fetched {OrderCount} orders for {AccountNumber}");

    private static readonly Action<ILogger, string, Exception?> LogCacheHit =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(22, nameof(LogCacheHit)),
            "Cache hit for {CacheKey}");

    private static readonly Action<ILogger, string, Exception?> LogRetryError =
        LoggerMessage.Define<string>(
            LogLevel.Warning,
            new EventId(23, nameof(LogRetryError)),
            "Retryable error: {ErrorMessage}");

    public KisDataService(HttpClient httpClient, IMemoryCache cache, IClock clock, ILogger<KisDataService> logger)
    {
        _httpClient = httpClient;
        _cache = cache;
        _clock = clock;
        _logger = logger;
    }

    /// <summary>
    /// Fetch trading orders for an account within date range.
    /// Implements caching (1h) and retry logic for transient failures.
    /// </summary>
    public async Task<IReadOnlyList<OrderItem>> GetTradingOrdersAsync(
        string accountNumber,
        DateOnly startDate,
        DateOnly endDate,
        CancellationToken cancellationToken)
    {
        LogFetchingOrders(_logger, accountNumber, null);

        var cacheKey = $"orders:{accountNumber}:{startDate:yyyyMMdd}:{endDate:yyyyMMdd}";

        // Check cache first
        if (_cache.TryGetValue(cacheKey, out IReadOnlyList<OrderItem>? cached))
        {
            LogCacheHit(_logger, cacheKey, null);
            return cached!;
        }

        // Fetch with exponential backoff retry
        var orders = new List<OrderItem>();
        int attempt = 0;
        int backoffMs = InitialBackoffMs;

        while (attempt < MaxRetries)
        {
            try
            {
                var response = await FetchOrdersFromApiAsync(
                    accountNumber,
                    startDate,
                    endDate,
                    cancellationToken);
                orders = ParseOrdersResponse(response);
                break;
            }
            catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests && attempt < MaxRetries - 1)
            {
                backoffMs = Math.Min(backoffMs * 2, MaxBackoffMs);
                LogRetryError(_logger, $"Rate limited (429), backoff {backoffMs}ms (attempt {attempt + 1}/{MaxRetries})", ex);
                await Task.Delay(backoffMs, cancellationToken);
                attempt++;
            }
            catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized && attempt < MaxRetries - 1)
            {
                // 401: Token expired → retry (token refresh happens upstream)
                LogRetryError(_logger, $"Token refresh needed (401), retry {attempt + 1}/{MaxRetries}", ex);
                await Task.Delay(2000, cancellationToken);
                attempt++;
            }
            catch (HttpRequestException ex) when (IsTransientError(ex) && attempt < MaxRetries - 1)
            {
                backoffMs = Math.Min(backoffMs * 2, MaxBackoffMs);
                LogRetryError(_logger, $"{ex.Message} (attempt {attempt + 1}/{MaxRetries})", ex);
                await Task.Delay(backoffMs, cancellationToken);
                attempt++;
            }
            catch (HttpRequestException ex) when (!IsTransientError(ex))
            {
                _logger.LogError(ex, "Permanent HTTP error fetching {AccountNumber}", accountNumber);
                throw;
            }
        }

        // Cache result
        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheDurationMinutes)
        };
        _cache.Set(cacheKey, (IReadOnlyList<OrderItem>)orders.AsReadOnly(), cacheOptions);

        LogFetchedOrders(_logger, accountNumber, orders.Count, null);
        return orders;
    }

    /// <summary>
    /// Fetch current portfolio holdings for position reconciliation.
    /// </summary>
    public async Task<IReadOnlyList<PositionItem>> GetPortfolioHoldingsAsync(
        string accountNumber,
        CancellationToken cancellationToken)
    {
        var cacheKey = $"positions:{accountNumber}";

        if (_cache.TryGetValue(cacheKey, out IReadOnlyList<PositionItem>? cached))
        {
            LogCacheHit(_logger, cacheKey, null);
            return cached!;
        }

        // Simplified: stub implementation
        // In production: fetch from KIS portfolio endpoint
        var positions = new List<PositionItem>
        {
            new(
                ticker: "005930",  // Samsung
                quantity: 100,
                currentPrice: 70000m,
                totalValue: 7000000m,
                asOfDate: DateOnly.FromDateTime(_clock.UtcNow.UtcDateTime))
        };

        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheDurationMinutes)
        };
        _cache.Set(cacheKey, (IReadOnlyList<PositionItem>)positions.AsReadOnly(), cacheOptions);

        return positions;
    }

    /// <summary>
    /// Execute a buy/sell order (production only, not used in shadow run).
    /// </summary>
    public async Task<OrderExecutionResult> ExecuteOrderAsync(
        string accountNumber,
        OrderRequest request,
        CancellationToken cancellationToken)
    {
        var apiKey = Environment.GetEnvironmentVariable("KIS_API_KEY") ?? "";

        if (string.IsNullOrEmpty(apiKey))
        {
            _logger.LogWarning("KIS_API_KEY not set; order execution disabled");
            return new OrderExecutionResult(
                success: false,
                orderId: "",
                errorMessage: "KIS API key not configured");
        }

        try
        {
            // KIS API: POST /oauth2/token (get OAuth2 token first)
            // Then: POST /uapi/domestic-stock/v1/trading/order-cash (execute order)
            // This is simplified; full implementation requires OAuth2 token refresh

            _logger.LogInformation("Would execute order for {Ticker} ({Side} {Quantity})",
                request.ticker, request.side, request.quantity);

            // Stub: return success with fake order ID
            return new OrderExecutionResult(
                success: true,
                orderId: Guid.NewGuid().ToString(),
                errorMessage: null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Order execution failed for {AccountNumber}", accountNumber);
            return new OrderExecutionResult(
                success: false,
                orderId: "",
                errorMessage: ex.Message);
        }
    }

    private async Task<string> FetchOrdersFromApiAsync(
        string accountNumber,
        DateOnly startDate,
        DateOnly endDate,
        CancellationToken cancellationToken)
    {
        var apiKey = Environment.GetEnvironmentVariable("KIS_API_KEY") ?? "";

        if (string.IsNullOrEmpty(apiKey))
        {
            _logger.LogWarning("KIS_API_KEY not set, using stub data");
            // Fallback to stub
            await Task.Delay(100, cancellationToken);
            return $$"""
            {
              "orders": [
                {"order_id": "ORD001", "ticker": "005930", "side": "BUY", "quantity": 100, "price": 70000, "executed_date": "{{startDate:yyyyMMdd}}", "status": "EXECUTED"}
              ]
            }
            """;
        }

        try
        {
            // KIS API: GET /uapi/domestic-stock/v1/trading/inquire-order?cano=ACCOUNT (simplified)
            var endpoint = $"{KisApiBaseUrl}/uapi/domestic-stock/v1/trading/inquire-order?cano={accountNumber}";

            var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
            request.Headers.Add("Authorization", $"Bearer {apiKey}");
            request.Headers.Add("appKey", apiKey);

            var response = await _httpClient.SendAsync(request, cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                _logger.LogWarning("KIS API returned {StatusCode}; using stub data", response.StatusCode);
                // Fallback to stub
                await Task.Delay(100, cancellationToken);
                return $$"""
                {
                  "orders": []
                }
                """;
            }

            return await response.Content.ReadAsStringAsync(cancellationToken);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogWarning(ex, "KIS API request failed; using stub data");
            // Fallback to stub
            await Task.Delay(100, cancellationToken);
            return $$"""
            {
              "orders": []
            }
            """;
        }
    }

    private List<OrderItem> ParseOrdersResponse(string jsonResponse)
    {
        var orders = new List<OrderItem>();

        try
        {
            using var doc = JsonDocument.Parse(jsonResponse);
            var root = doc.RootElement;

            if (!root.TryGetProperty("orders", out var ordersElement))
            {
                return orders;
            }

            foreach (var element in ordersElement.EnumerateArray())
            {
                try
                {
                    var item = new OrderItem(
                        orderId: element.GetProperty("order_id").GetString() ?? "",
                        ticker: element.GetProperty("ticker").GetString() ?? "",
                        side: element.GetProperty("side").GetString() ?? "",
                        quantity: element.GetProperty("quantity").GetInt32(),
                        price: element.GetProperty("price").GetDecimal(),
                        executedDate: DateOnly.ParseExact(
                            element.GetProperty("executed_date").GetString() ?? "20000101",
                            "yyyyMMdd"),
                        status: element.GetProperty("status").GetString() ?? "");

                    orders.Add(item);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to parse order element");
                }
            }
        }
        catch (JsonException ex)
        {
            _logger.LogWarning(ex, "Failed to deserialize orders response");
        }

        return orders;
    }

    private static bool IsTransientError(HttpRequestException ex)
    {
        // 429: Too Many Requests (rate limit)
        // 503: Service Unavailable
        // 504: Gateway Timeout
        // 408: Request Timeout
        // 502: Bad Gateway
        return ex.StatusCode == HttpStatusCode.TooManyRequests
            || ex.StatusCode == HttpStatusCode.ServiceUnavailable
            || ex.StatusCode == HttpStatusCode.GatewayTimeout
            || ex.StatusCode == HttpStatusCode.RequestTimeout
            || ex.StatusCode == HttpStatusCode.BadGateway
            || (ex.InnerException is TimeoutException);
    }
}

public record OrderItem(
    string orderId,
    string ticker,
    string side,
    int quantity,
    decimal price,
    DateOnly executedDate,
    string status);

public record PositionItem(
    string ticker,
    int quantity,
    decimal currentPrice,
    decimal totalValue,
    DateOnly asOfDate);

public record OrderRequest(
    string ticker,
    string side,
    int quantity);

public record OrderExecutionResult(
    bool success,
    string orderId,
    string? errorMessage);
