using AuthApi.Models;
using AuthApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace AuthApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly UserService _users;
    private readonly IConfiguration _cfg;
    private readonly BlacklistService _blacklist;

    public AuthController(UserService users, IConfiguration cfg, BlacklistService blacklist)
    {
        _users = users; _cfg = cfg; _blacklist = blacklist;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterRequest req)
    {
        if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password))
            return BadRequest("Username and password required");

        if (await _users.GetByUsernameAsync(req.Username) != null)
            return BadRequest("User already exists");

        var hash = BCrypt.Net.BCrypt.HashPassword(req.Password);
        var user = new User { Username = req.Username, PasswordHash = hash, Role = "USER", Email = req.Email };
        await _users.CreateAsync(user);
        return Ok("User created");
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest req)
    {
        var user = await _users.GetByUsernameAsync(req.Username);
        if (user == null || !BCrypt.Net.BCrypt.Verify(req.Password, user.PasswordHash))
            return Unauthorized();

        var (accessToken, jti, expUtc) = GenerateJwtToken(user);
        user.RefreshToken = Guid.NewGuid().ToString("N");
        user.RefreshTokenExpiry = DateTime.UtcNow.AddDays(7);
        await _users.UpdateAsync(user);

        return Ok(new { accessToken, refreshToken = user.RefreshToken, user.Username, user.Role, jti, exp = expUtc });
    }

    [HttpPost("refresh")]
    public async Task<IActionResult> Refresh([FromBody] RefreshRequest req)
    {
        var user = await _users.GetByUsernameAsync(req.Username);
        if (user == null || user.RefreshToken != req.RefreshToken || user.RefreshTokenExpiry < DateTime.UtcNow)
            return Unauthorized("Invalid or expired refresh token");

        var (accessToken, _, expUtc) = GenerateJwtToken(user);
        return Ok(new { accessToken, exp = expUtc });
    }

    [HttpPost("logout")]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> Logout()
    {
        var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
        if (string.IsNullOrWhiteSpace(token)) return BadRequest("Token missing");
        var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
        await _blacklist.AddToBlacklistAsync(jwt.Id, jwt.ValidTo);
        return Ok("Logged out.");
    }

    [HttpPost("role")]
    [Authorize(Roles = "SUPER")]
    public async Task<IActionResult> ChangeUserRole([FromBody] ChangeRoleRequest req)
    {
        if (req.NewRole is not ("USER" or "SUPER")) return BadRequest("Role must be 'USER' or 'SUPER'");
        var user = await _users.GetByUsernameAsync(req.Username);
        if (user is null) return NotFound("User not found");
        user.Role = req.NewRole;
        await _users.UpdateAsync(user);
        return Ok($"{req.Username}'s role updated to {req.NewRole}");
    }

    [HttpGet("users")]
    [Authorize(Roles = "SUPER")]
    public async Task<IActionResult> GetAllUsers() => Ok(await _users.GetAllAsync());

    private (string token, string jti, DateTime expUtc) GenerateJwtToken(User user)
    {
        var key = Encoding.UTF8.GetBytes(_cfg["Jwt:Key"]!);
        var issuer = _cfg["Jwt:Issuer"] ?? "GameAuthApi";
        var audience = _cfg["Jwt:Audience"] ?? issuer;

        var creds = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
        var jti = Guid.NewGuid().ToString("N");
        var claims = new[]
        {
            new Claim(ClaimTypes.Name, user.Username),
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Role, user.Role),
            new Claim(JwtRegisteredClaimNames.Jti, jti)
        };

        var exp = DateTime.UtcNow.AddMinutes(15);
        var token = new JwtSecurityToken(issuer, audience, claims, expires: exp, signingCredentials: creds);
        return (new JwtSecurityTokenHandler().WriteToken(token), jti, exp);
    }
}
