using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Dapper;
using QuantEngine.Core.Interfaces;
using QuantEngine.Infrastructure.Data;

namespace QuantEngine.Infrastructure.External
{
    public class KisCredentials
    {
        public string AppKey { get; }
        public string AppSecret { get; }
        public string Account { get; } // "real" | "mock"
        public string Domain { get; }

        public KisCredentials(string appKey, string appSecret, string account)
        {
            AppKey = appKey;
            AppSecret = appSecret;
            Account = account;
            Domain = account == "real" 
                ? "https://openapi.koreainvestment.com:9443" 
                : "https://openapivts.koreainvestment.com:29443";
        }

        public static KisCredentials Load(string account = "mock")
        {
            string keyVar = account == "real" ? "KIS_APP_Key" : "KIS_APP_Key_TEST";
            string secretVar = account == "real" ? "KIS_APP_Secret" : "KIS_APP_Secret_TEST";

            string? appKey = Environment.GetEnvironmentVariable(keyVar);
            string? appSecret = Environment.GetEnvironmentVariable(secretVar);

            if (string.IsNullOrEmpty(appKey) || string.IsNullOrEmpty(appSecret))
            {
                // Fallback registry checks are not cross-platform and environment variables should be defined.
                // In production/Linux it is env-only.
                throw new InvalidOperationException(
                    $"KIS Credentials Environment Variables missing: {keyVar} or {secretVar}."
                );
            }

            return new KisCredentials(appKey, appSecret, account);
        }
    }

    public class KisApiClient : IKisApiClient
    {
        private readonly HttpClient _httpClient;
        private readonly IDbConnectionFactory _dbConnectionFactory;
        private readonly KisCredentials _creds;

        private static readonly string[] ForbiddenPathSubstrings = { "/trading/" };
        private static readonly string[] ForbiddenTrIdPrefixes = { "TTTC08", "VTTC08", "TTTC01", "VTTC01", "TTTC8434R", "VTTC8434R" };

        public KisApiClient(HttpClient httpClient, IDbConnectionFactory dbConnectionFactory, string account = "mock")
        {
            _httpClient = httpClient;
            _dbConnectionFactory = dbConnectionFactory;
            _creds = KisCredentials.Load(account);
        }

        private void AssertReadOnly(string path, string trId)
        {
            foreach (var forbidden in ForbiddenPathSubstrings)
            {
                if (path.Contains(forbidden, StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException($"BLOCKED: 주문 관련 경로 호출 시도 차단 — path={path}");
                }
            }
            foreach (var prefix in ForbiddenTrIdPrefixes)
            {
                if (trId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException($"BLOCKED: 주문 관련 TR_ID 호출 시도 차단 — tr_id={trId}");
                }
            }
        }

        private async Task<string> IssueOrReuseTokenAsync()
        {
            using var conn = _dbConnectionFactory.CreateConnection();
            conn.Open();

            // 1. Try to load cached token
            var cached = await conn.QueryFirstOrDefaultAsync<(string access_token, string expires_at)>(
                "SELECT access_token, expires_at FROM quantengine.kis_tokens WHERE account = @Account",
                new { Account = _creds.Account }
            );

            if (cached.access_token != null)
            {
                if (DateTime.TryParse(cached.expires_at, out var expiresAtUtc))
                {
                    // Reuse token if it has more than 10 minutes left before expiration
                    if (DateTime.UtcNow < expiresAtUtc.AddMinutes(-10))
                    {
                        return cached.access_token;
                    }
                }
            }

            // 2. Request new token from KIS API
            var requestUrl = $"{_creds.Domain}/oauth2/tokenP";
            var requestBody = new
            {
                grant_type = "client_credentials",
                appkey = _creds.AppKey,
                appsecret = _creds.AppSecret
            };

            var response = await _httpClient.PostAsJsonAsync(requestUrl, requestBody);
            response.EnsureSuccessStatusCode();

            var resData = await response.Content.ReadFromJsonAsync<JsonElement>();
            var accessToken = resData.GetProperty("access_token").GetString() 
                ?? throw new InvalidOperationException("Failed to parse access_token from response.");
            var expiresInSec = resData.GetProperty("expires_in").GetInt32();
            var expiresAt = DateTime.UtcNow.AddSeconds(expiresInSec);

            // 3. Upsert token cache into PG database
            await conn.ExecuteAsync(@"
                INSERT INTO quantengine.kis_tokens (account, access_token, expires_at, updated_at)
                VALUES (@Account, @AccessToken, @ExpiresAt, @UpdatedAt)
                ON CONFLICT (account) DO UPDATE SET
                    access_token = EXCLUDED.access_token,
                    expires_at = EXCLUDED.expires_at,
                    updated_at = EXCLUDED.updated_at",
                new
                {
                    Account = _creds.Account,
                    AccessToken = accessToken,
                    ExpiresAt = expiresAt.ToString("o"),
                    UpdatedAt = DateTime.UtcNow.ToString("o")
                }
            );

            return accessToken;
        }

        private async Task<string> SendRequestAsync(string path, string trId, Dictionary<string, string> queryParams)
        {
            AssertReadOnly(path, trId);
            var token = await IssueOrReuseTokenAsync();

            var queryBuilder = new List<string>();
            foreach (var kvp in queryParams)
            {
                queryBuilder.Add($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
            }
            var fullUrl = $"{_creds.Domain}{path}?{string.Join("&", queryBuilder)}";

            using var request = new HttpRequestMessage(HttpMethod.Get, fullUrl);
            request.Headers.Accept.Clear();
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Add("authorization", $"Bearer {token}");
            request.Headers.Add("appkey", _creds.AppKey);
            request.Headers.Add("appsecret", _creds.AppSecret);
            request.Headers.Add("tr_id", trId);
            request.Headers.Add("custtype", "P");

            var response = await _httpClient.SendAsync(request);
            response.EnsureSuccessStatusCode();

            return await response.Content.ReadAsStringAsync();
        }

        public async Task<Dictionary<string, object>> GetCurrentPriceAsync(string code, string account = "mock")
        {
            var json = await SendRequestAsync(
                "/uapi/domestic-stock/v1/quotations/inquire-price",
                "FHKST01010100",
                new Dictionary<string, string>
                {
                    { "FID_COND_MRKT_DIV_CODE", "J" },
                    { "FID_INPUT_ISCD", code }
                }
            );
            return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
        }

        public async Task<Dictionary<string, object>> GetAskingPrice10LevelAsync(string code, string account = "mock")
        {
            var json = await SendRequestAsync(
                "/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn",
                "FHKST01010200",
                new Dictionary<string, string>
                {
                    { "FID_COND_MRKT_DIV_CODE", "J" },
                    { "FID_INPUT_ISCD", code }
                }
            );
            return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
        }

        public async Task<Dictionary<string, object>> GetDailyShortSaleAsync(string code, string startDate, string endDate, string account = "mock")
        {
            var json = await SendRequestAsync(
                "/uapi/domestic-stock/v1/quotations/daily-short-sale",
                "FHPST04830000",
                new Dictionary<string, string>
                {
                    { "FID_COND_MRKT_DIV_CODE", "J" },
                    { "FID_INPUT_ISCD", code },
                    { "FID_INPUT_DATE_1", startDate },
                    { "FID_INPUT_DATE_2", endDate }
                }
            );
            return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
        }

        public async Task<Dictionary<string, object>> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D", string account = "mock")
        {
            var json = await SendRequestAsync(
                "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice",
                "FHKST03010100",
                new Dictionary<string, string>
                {
                    { "FID_COND_MRKT_DIV_CODE", "J" },
                    { "FID_INPUT_ISCD", code },
                    { "FID_INPUT_DATE_1", startDate },
                    { "FID_INPUT_DATE_2", endDate },
                    { "FID_PERIOD_DIV_CODE", period },
                    { "FID_ORG_ADJ_PRC", "0" }
                }
            );
            return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
        }

        public async Task<Dictionary<string, object>> GetInvestorTrendAsync(string code, string account = "mock")
        {
            var json = await SendRequestAsync(
                "/uapi/domestic-stock/v1/quotations/inquire-investor",
                "FHKST01010900",
                new Dictionary<string, string>
                {
                    { "FID_COND_MRKT_DIV_CODE", "J" },
                    { "FID_INPUT_ISCD", code }
                }
            );
            return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
        }
    }
}
