using Kalaha.Api.Database;
using Kalaha.Api.Database.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;


namespace Kalaha.Api.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly KalahaDbContext _context;
        private readonly IConfiguration _configuration;

        public AuthController(KalahaDbContext context, IConfiguration configuration)
        {
            _context = context;
            _configuration = configuration;
        }

        public class UserLoginDto
        {
            public string Username { get; set; } = string.Empty;
            public string Password { get; set; } = string.Empty;
        }

        public class TokenDto
        {
            public string AccessToken { get; set; } = "";
            public string RefreshToken { get; set; } = "";
        }

        [HttpPost("register")]
        public async Task<IActionResult> Register([FromBody] UserLoginDto request)
        {
            if (await _context.Users.AnyAsync(u => u.Name == request.Username))
            {
                return BadRequest("Dieser Benutzername ist bereits vergeben.");
            }

            Regex regex = new Regex(@"^[a-zA-Z0-9_-]+$");
            if (request.Username.Length > 30 || !regex.IsMatch(request.Username))
            {
                return BadRequest("Ungültiger Username. Verwende a-z, A-Z, 0-9, -, _ und bis max 30 Zeichen");
            }

            if (request.Password.Length < 8)
            {
                return BadRequest("Passwort muss min. 8 Zeichen beinhalten.");
            }

            string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);

            var user = new User
            {
                Name = request.Username,
                PasswordHash = passwordHash
            };

            _context.Users.Add(user);
            await _context.SaveChangesAsync();

            return Ok(new { message = "Erfolgreich registriert. Du kannst dich jetzt einloggen." });
        }

        [HttpPost("login")]
        public async Task<IActionResult> Login([FromBody] UserLoginDto request)
        {
            var user = await _context.Users.FirstOrDefaultAsync(u => u.Name == request.Username);
            if (user == null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
            {
                return BadRequest("Login fehlgeschlagen");
            }

            TokenDto loginResponseDto = new TokenDto()
            {
                AccessToken = CreateJwtAccessToken(user),
                RefreshToken = GenerateRefreshToken()
            };

            user.RefreshToken = loginResponseDto.RefreshToken;
            user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7);

            await _context.SaveChangesAsync();

            return Ok(loginResponseDto);
        }


        [HttpPost("refresh")]
        public async Task<IActionResult> Refresh([FromBody] TokenDto request)
        {
            // Wir lesen den Principal aus dem abgelaufenen AccessToken aus
            var principal = GetPrincipalFromExpiredToken(request.AccessToken);
            if (principal == null) return BadRequest("Ungültiger Access Token");

            // Über den Namen identifizieren wir den User
            var username = principal.Identity?.Name;
            var user = await _context.Users.SingleOrDefaultAsync(u => u.Name == username);

            // Validierung des Refresh Tokens
            if (user == null ||
                user.RefreshToken != request.RefreshToken ||
                user.RefreshTokenExpiryTime <= DateTime.UtcNow)
            {
                return Unauthorized("Ungültiger oder abgelaufener Refresh Token. Bitte neu einloggen.");
            }

            // 3. Rotation: Neue Tokens generieren! (Sicherheits-Best-Practice)
            var newAccessToken = CreateJwtAccessToken(user);
            var newRefreshToken = GenerateRefreshToken();

            user.RefreshToken = newRefreshToken;
            // Ablaufdatum erneut verlängern!
            user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7);
            
            await _context.SaveChangesAsync();

            return Ok(new TokenDto { AccessToken = newAccessToken, RefreshToken = newRefreshToken });
        }

        private string CreateJwtAccessToken(User user)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Name)
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
                _configuration["Jwt:Key"] ?? "MeinSuperGeheimesUndLangesPasswortFuerKalaha123!"));

            // Einheitlich HmacSha256 verwenden (passt zu GetPrincipalFromExpiredToken)
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: _configuration["Jwt:Issuer"] ?? "KalahaApi",
                audience: _configuration["Jwt:Audience"] ?? "KalahaFrontend",
                claims: claims,
                expires: DateTime.UtcNow.AddMinutes(15), // Access Token sollte kurzlebig sein (z.B. 15 Min)
                signingCredentials: creds
            );

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

        private string GenerateRefreshToken()
        {
            var randomNumber = new byte[64];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }

        private ClaimsPrincipal? GetPrincipalFromExpiredToken(string? token)
        {
            if (string.IsNullOrEmpty(token)) return null;

            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = true,
                ValidAudience = _configuration["Jwt:Audience"] ?? "KalahaFrontend",
                ValidateIssuer = true,
                ValidIssuer = _configuration["Jwt:Issuer"] ?? "KalahaApi",
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
                    _configuration["Jwt:Key"] ?? "MeinSuperGeheimesUndLangesPasswortFuerKalaha123!")),
                ValidateLifetime = false // WICHTIG: Hier prüfen wir einen ABGELAUFENEN Token
            };

            var tokenHandler = new JwtSecurityTokenHandler();

            try
            {
                var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken securityToken);

                var jwtSecurityToken = securityToken as JwtSecurityToken;
                // Algorithmus muss mit dem in CreateJwtAccessToken übereinstimmen (HmacSha256)
                if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new SecurityTokenException("Ungültiger Token-Algorithmus");
                }

                return principal;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Token-Validierung fehlgeschlagen: {ex.Message}");
                return null;
            }
        }

    }
}
