using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Data.Entities;

namespace OTSSignsOrchestrator.Server.Auth;

public class OperatorAuthService
{
    private readonly OrchestratorDbContext _db;
    private readonly JwtOptions _jwt;
    private readonly ILogger<OperatorAuthService> _logger;

    public OperatorAuthService(
        OrchestratorDbContext db,
        IOptions<JwtOptions> jwt,
        ILogger<OperatorAuthService> logger)
    {
        _db = db;
        _jwt = jwt.Value;
        _logger = logger;
    }

    public async Task<(string Jwt, string RefreshToken)> LoginAsync(string email, string password)
    {
        var op = await _db.Operators.FirstOrDefaultAsync(
            o => o.Email == email.Trim().ToLowerInvariant());

        if (op is null || !BCrypt.Net.BCrypt.Verify(password, op.PasswordHash))
        {
            _logger.LogWarning("Login failed for {Email}", email);
            throw new UnauthorizedAccessException("Invalid email or password.");
        }

        _logger.LogInformation("Operator {Email} logged in", op.Email);
        var jwt = GenerateJwt(op);
        var refresh = await CreateRefreshTokenAsync(op.Id);
        return (jwt, refresh);
    }

    public async Task<string> RefreshAsync(string refreshToken)
    {
        var token = await _db.RefreshTokens
            .Include(r => r.Operator)
            .FirstOrDefaultAsync(r => r.Token == refreshToken);

        if (token is null || token.RevokedAt is not null || token.ExpiresAt < DateTime.UtcNow)
            throw new UnauthorizedAccessException("Invalid or expired refresh token.");

        // Revoke the used token (single-use rotation)
        token.RevokedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync();

        _logger.LogInformation("Refresh token used for operator {Email}", token.Operator.Email);
        return GenerateJwt(token.Operator);
    }

    private string GenerateJwt(Operator op)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, op.Id.ToString()),
            new Claim(JwtRegisteredClaimNames.Email, op.Email),
            new Claim(ClaimTypes.Name, op.Email),
            new Claim(ClaimTypes.Role, op.Role.ToString()),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };

        var token = new JwtSecurityToken(
            issuer: _jwt.Issuer,
            audience: _jwt.Audience,
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(15),
            signingCredentials: creds);

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

    private async Task<string> CreateRefreshTokenAsync(Guid operatorId)
    {
        var tokenValue = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));

        _db.RefreshTokens.Add(new RefreshToken
        {
            Id = Guid.NewGuid(),
            OperatorId = operatorId,
            Token = tokenValue,
            ExpiresAt = DateTime.UtcNow.AddDays(7),
        });

        await _db.SaveChangesAsync();
        return tokenValue;
    }
}
