using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;

namespace AxCopilot.Services;

/// <summary>
/// IBM Cloud Pak for Data (CP4D) 토큰 발급 및 캐싱 서비스.
/// CP4D의 /icp4d-api/v1/authorize 엔드포인트에서 Bearer 토큰을 발급받고,
/// 만료 전까지 캐싱하여 재사용합니다.
/// </summary>
internal sealed class Cp4dTokenService
{
    private static readonly HttpClient _http = new()
    {
        Timeout = TimeSpan.FromSeconds(15)
    };

    // 캐시: (cp4dUrl + username) → (token, expiry)
    private static readonly Dictionary<string, (string Token, DateTime Expiry)> _cache = new();
    private static readonly object _lock = new();

    /// <summary>
    /// CP4D 토큰을 발급받거나 캐시에서 반환합니다.
    /// </summary>
    /// <param name="cp4dUrl">CP4D 서버 URL (예: https://cpd-host.example.com)</param>
    /// <param name="username">CP4D 사용자 이름</param>
    /// <param name="password">CP4D 비밀번호 또는 API 키</param>
    /// <param name="ct">취소 토큰</param>
    /// <returns>Bearer 토큰 문자열. 실패 시 null.</returns>
    public static async Task<string?> GetTokenAsync(string cp4dUrl, string username, string password, CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(cp4dUrl) || string.IsNullOrWhiteSpace(username))
            return null;

        var cacheKey = $"{cp4dUrl}|{username}";

        // 캐시 확인 — 만료 1분 전까지 유효
        lock (_lock)
        {
            if (_cache.TryGetValue(cacheKey, out var cached) && cached.Expiry > DateTime.UtcNow.AddMinutes(1))
                return cached.Token;
        }

        // 토큰 발급
        try
        {
            var tokenUrl = cp4dUrl.TrimEnd('/') + "/icp4d-api/v1/authorize";
            var body = new { username, password };

            using var req = new HttpRequestMessage(HttpMethod.Post, tokenUrl)
            {
                Content = JsonContent.Create(body)
            };

            // CP4D는 자체 서명 인증서를 사용할 수 있으므로 TLS 오류 무시 옵션 (사내 환경)
            using var resp = await _http.SendAsync(req, ct);

            if (!resp.IsSuccessStatusCode)
            {
                var errBody = await resp.Content.ReadAsStringAsync(ct);
                LogService.Warn($"CP4D 토큰 발급 실패: {resp.StatusCode} - {errBody}");
                return null;
            }

            var json = await resp.Content.ReadAsStringAsync(ct);
            using var doc = JsonDocument.Parse(json);

            // CP4D 응답 형식: {"token": "...", "_messageCode_": "...", "message": "..."}
            if (!doc.RootElement.TryGetProperty("token", out var tokenProp))
            {
                LogService.Warn("CP4D 응답에 token 필드가 없습니다.");
                return null;
            }

            var token = tokenProp.GetString();
            if (string.IsNullOrEmpty(token)) return null;

            // 토큰 만료 시간 — CP4D 기본 12시간, 안전하게 11시간으로 설정
            var expiry = DateTime.UtcNow.AddHours(11);

            // _messageCode_에서 만료 정보가 있으면 파싱 시도
            if (doc.RootElement.TryGetProperty("accessTokenExpiry", out var expiryProp) &&
                expiryProp.TryGetInt64(out var expiryMs))
            {
                expiry = DateTimeOffset.FromUnixTimeMilliseconds(expiryMs).UtcDateTime;
            }

            lock (_lock)
            {
                _cache[cacheKey] = (token, expiry);
            }

            LogService.Info($"CP4D 토큰 발급 완료: {cp4dUrl} (만료: {expiry:yyyy-MM-dd HH:mm} UTC)");
            return token;
        }
        catch (Exception ex)
        {
            LogService.Error($"CP4D 토큰 발급 오류: {ex.Message}");
            return null;
        }
    }

    /// <summary>특정 CP4D 서버의 캐시된 토큰을 무효화합니다.</summary>
    public static void InvalidateToken(string cp4dUrl, string username)
    {
        var cacheKey = $"{cp4dUrl}|{username}";
        lock (_lock) { _cache.Remove(cacheKey); }
    }

    /// <summary>모든 캐시된 토큰을 초기화합니다.</summary>
    public static void ClearAllTokens()
    {
        lock (_lock) { _cache.Clear(); }
    }
}
