using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace ScadaLink.Security;

public class JwtTokenService
{
    private readonly SecurityOptions _options;
    private readonly ILogger<JwtTokenService> _logger;

    public const string DisplayNameClaimType = "DisplayName";
    public const string UsernameClaimType = "Username";
    public const string RoleClaimType = "Role";
    public const string SiteIdClaimType = "SiteId";
    public const string LastActivityClaimType = "LastActivity";

    public JwtTokenService(IOptions<SecurityOptions> options, ILogger<JwtTokenService> logger)
    {
        _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public string GenerateToken(
        string displayName,
        string username,
        IReadOnlyList<string> roles,
        IReadOnlyList<string>? permittedSiteIds)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.JwtSigningKey));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim>
        {
            new(DisplayNameClaimType, displayName),
            new(UsernameClaimType, username),
            new(LastActivityClaimType, DateTimeOffset.UtcNow.ToString("o"))
        };

        foreach (var role in roles)
        {
            claims.Add(new Claim(RoleClaimType, role));
        }

        if (permittedSiteIds != null)
        {
            foreach (var siteId in permittedSiteIds)
            {
                claims.Add(new Claim(SiteIdClaimType, siteId));
            }
        }

        var token = new JwtSecurityToken(
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(_options.JwtExpiryMinutes),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public ClaimsPrincipal? ValidateToken(string token)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.JwtSigningKey));
        var validationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = key,
            ClockSkew = TimeSpan.Zero
        };

        try
        {
            var handler = new JwtSecurityTokenHandler();
            var principal = handler.ValidateToken(token, validationParameters, out _);
            return principal;
        }
        catch (Exception ex) when (ex is SecurityTokenException or ArgumentException)
        {
            _logger.LogDebug(ex, "Token validation failed");
            return null;
        }
    }

    public bool ShouldRefresh(ClaimsPrincipal principal)
    {
        var expClaim = principal.FindFirst("exp");
        if (expClaim == null || !long.TryParse(expClaim.Value, out var expUnix))
            return false;

        var expiry = DateTimeOffset.FromUnixTimeSeconds(expUnix);
        var remaining = expiry - DateTimeOffset.UtcNow;

        return remaining.TotalMinutes < _options.JwtRefreshThresholdMinutes;
    }

    public bool IsIdleTimedOut(ClaimsPrincipal principal)
    {
        var lastActivityClaim = principal.FindFirst(LastActivityClaimType);
        if (lastActivityClaim == null || !DateTimeOffset.TryParse(lastActivityClaim.Value, out var lastActivity))
            return true;

        return (DateTimeOffset.UtcNow - lastActivity).TotalMinutes > _options.IdleTimeoutMinutes;
    }

    public string? RefreshToken(ClaimsPrincipal currentPrincipal, IReadOnlyList<string> currentRoles, IReadOnlyList<string>? permittedSiteIds)
    {
        var displayName = currentPrincipal.FindFirst(DisplayNameClaimType)?.Value;
        var username = currentPrincipal.FindFirst(UsernameClaimType)?.Value;

        if (displayName == null || username == null)
        {
            _logger.LogWarning("Cannot refresh token: missing DisplayName or Username claims");
            return null;
        }

        return GenerateToken(displayName, username, currentRoles, permittedSiteIds);
    }
}
