using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using TaxBaik.Application.Services;

namespace TaxBaik.Web.Client.Components.Admin.Services;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly ITokenStore _tokenStore;
    private readonly IApiClient _apiClient;
    private readonly IAdminSessionBootstrap _sessionBootstrap;
    private readonly ILocalStorageService _localStorage;
    private readonly ILogger<CustomAuthenticationStateProvider> _logger;

    public CustomAuthenticationStateProvider(
        ITokenStore tokenStore,
        IApiClient apiClient,
        IAdminSessionBootstrap sessionBootstrap,
        ILocalStorageService localStorage,
        ILogger<CustomAuthenticationStateProvider> logger)
    {
        _tokenStore = tokenStore;
        _apiClient = apiClient;
        _sessionBootstrap = sessionBootstrap;
        _localStorage = localStorage;
        _logger = logger;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        try
        {
            await _sessionBootstrap.EnsureInitializedAsync();

            var accessToken = _tokenStore.AccessToken;

            if (string.IsNullOrEmpty(_tokenStore.AccessToken))
            {
                return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
            }

            // 토큰이 만료되면 로그아웃
            if (_tokenStore.IsAccessTokenExpired())
            {
                _logger.LogWarning("Access token 만료됨 - 자동 로그아웃");
                await LogoutAsync();
                return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
            }

            // 토큰이 5분 이내로 만료되면 자동 갱신 시도 (사용자 경험 향상)
            if (!string.IsNullOrEmpty(_tokenStore.RefreshToken) && ShouldRefreshToken())
            {
                _logger.LogInformation("토큰 만료 5분 전 - 자동 갱신 시작");
                var request = new { RefreshToken = _tokenStore.RefreshToken };
                var newTokenPair = await _apiClient.PostAsync<WasmAuthTokenPair>("auth/refresh", request);
                if (newTokenPair != null && !string.IsNullOrEmpty(newTokenPair.AccessToken))
                {
                    await LoginAsync(newTokenPair.AccessToken, newTokenPair.RefreshToken, newTokenPair.ExpiresIn);
                    _logger.LogInformation("토큰 자동 갱신 성공");
                    accessToken = newTokenPair.AccessToken;
                }
                else
                {
                    _logger.LogWarning("토큰 자동 갱신 실패 - 로그아웃");
                    await LogoutAsync();
                    return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
                }
            }

            var principal = ValidateTokenWithoutDb(accessToken ?? string.Empty);
            if (principal == null)
            {
                await LogoutAsync();
                return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
            }

            return new AuthenticationState(principal);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "❌ 인증 상태 조회 중 오류 발생: {ExceptionType} - {Message} - {StackTrace}",
                ex.GetType().Name, ex.Message, ex.StackTrace);
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }
    }

    private ClaimsPrincipal? ValidateTokenWithoutDb(string token)
    {
        try
        {
            if (string.IsNullOrEmpty(token))
            {
                _logger.LogWarning("❌ 토큰이 비어있습니다");
                return null;
            }

            var handler = new JwtSecurityTokenHandler();
            var jwtToken = handler.ReadJwtToken(token);
            var identity = new ClaimsIdentity(jwtToken.Claims, "jwt");
            _logger.LogInformation("✅ 토큰 검증 성공: {Claims} claims", jwtToken.Claims.Count());
            return new ClaimsPrincipal(identity);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "❌ 토큰 검증 실패: {ExceptionType} - {Message}", ex.GetType().Name, ex.Message);
            return null;
        }
    }


    public async Task LoginAsync(string accessToken, string refreshToken, int expiresIn)
    {
        // 토큰 만료 시간을 .NET ticks로 계산 (JavaScript와 일치)
        // JavaScript: 621355968000000000 + ((Date.now() + expiresIn*1000) * 10000)
        // 이를 C#로 변환하면 DateTime.UtcNow.AddSeconds(expiresIn).Ticks
        var tokenExpiryTicks = DateTime.UtcNow.AddSeconds(expiresIn).Ticks;

        // TokenStore에 저장 (DelegatingHandler에서 사용)
        _tokenStore.AccessToken = accessToken;
        _tokenStore.RefreshToken = refreshToken;
        _tokenStore.TokenExpiryTicks = tokenExpiryTicks;

        // Blazor에 인증 상태 변경을 알림 - 이 호출 자체는 async이지만 fire-and-forget OK
        // (NotifyAuthenticationStateChanged는 내부적으로 Task를 구독함)
        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }

    private static bool TryNormalizeExpiryTicks(string? rawValue, out long ticks)
    {
        ticks = 0;
        if (!long.TryParse(rawValue, out var parsed))
        {
            return false;
        }

        // Support both legacy Unix-millisecond storage and .NET ticks.
        if (parsed > 10_000_000_000_000L && parsed < 100_000_000_000_000_000L)
        {
            ticks = parsed;
            return true;
        }

        if (parsed > 1_000_000_000_000L && parsed < 100_000_000_000_000L)
        {
            ticks = DateTimeOffset.FromUnixTimeMilliseconds(parsed).UtcDateTime.Ticks;
            return true;
        }

        return false;
    }

    private bool ShouldRefreshToken()
    {
        // 토큰이 5분 이내로 만료되면 갱신 (300초 = 5분)
        if (!_tokenStore.TokenExpiryTicks.HasValue || _tokenStore.TokenExpiryTicks.Value <= 0)
            return false;

        const int refreshThresholdSeconds = 300;
        try
        {
            var expiryTime = new DateTime(_tokenStore.TokenExpiryTicks.Value, DateTimeKind.Utc);
            var timeUntilExpiry = expiryTime - DateTime.UtcNow;
            return timeUntilExpiry.TotalSeconds <= refreshThresholdSeconds && timeUntilExpiry.TotalSeconds > 0;
        }
        catch
        {
            return false;
        }
    }

    public async Task LogoutAsync()
    {
        // TokenStore 초기화
        _tokenStore.Clear();
        await _localStorage.ClearAuthAsync();

        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }

    private bool IsTokenExpired(string token)
    {
        try
        {
            var handler = new JwtSecurityTokenHandler();
            var jwtToken = handler.ReadJwtToken(token);
            return jwtToken.ValidTo < DateTime.UtcNow;
        }
        catch
        {
            return true;
        }
    }
}

public class WasmAuthTokenPair
{
    public WasmAuthTokenPair() { }
    public WasmAuthTokenPair(string accessToken, string refreshToken, int expiresIn)
    {
        AccessToken = accessToken;
        RefreshToken = refreshToken;
        ExpiresIn = expiresIn;
    }
    public string AccessToken { get; set; } = "";
    public string RefreshToken { get; set; } = "";
    public int ExpiresIn { get; set; }
}
