using System.Security.Cryptography;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using Microsoft.EntityFrameworkCore;
using RR3CommunityServer.Data;
using RR3CommunityServer.Models;

namespace RR3CommunityServer.Services;

public interface IAuthService
{
    Task<(bool Success, string? Token, string? Error)> RegisterAsync(RegisterRequest request);
    Task<(bool Success, LoginResponse? Response, string? Error)> LoginAsync(LoginRequest request);
    Task<(bool Success, string? Error)> ChangePasswordAsync(int accountId, ChangePasswordRequest request);
    Task<(bool Success, string? Error)> ForgotPasswordAsync(ForgotPasswordRequest request);
    Task<(bool Success, string? Error)> ResetPasswordAsync(ResetPasswordRequest request);
    Task<(bool Success, string? Error)> LinkDeviceAsync(int accountId, LinkDeviceRequest request);
    Task<(bool Success, string? Error)> UnlinkDeviceAsync(int accountId, string deviceId);
    Task<AccountSettingsResponse?> GetAccountSettingsAsync(int accountId);
    Task<Account?> ValidateTokenAsync(string token);
}

public class AuthService : IAuthService
{
    private readonly RR3DbContext _context;
    private readonly IConfiguration _configuration;
    private readonly ILogger<AuthService> _logger;

    public AuthService(RR3DbContext context, IConfiguration configuration, ILogger<AuthService> logger)
    {
        _context = context;
        _configuration = configuration;
        _logger = logger;
    }

    public async Task<(bool Success, string? Token, string? Error)> RegisterAsync(RegisterRequest request)
    {
        // Validate input
        if (string.IsNullOrWhiteSpace(request.Username) || request.Username.Length < 3)
            return (false, null, "Username must be at least 3 characters");

        if (string.IsNullOrWhiteSpace(request.Email) || !request.Email.Contains('@'))
            return (false, null, "Invalid email address");

        if (string.IsNullOrWhiteSpace(request.Password) || request.Password.Length < 6)
            return (false, null, "Password must be at least 6 characters");

        if (request.Password != request.ConfirmPassword)
            return (false, null, "Passwords do not match");

        // Check if username or email already exists
        var existingUsername = await _context.Set<Account>()
            .AnyAsync(a => a.Username.ToLower() == request.Username.ToLower());
        if (existingUsername)
            return (false, null, "Username already taken");

        var existingEmail = await _context.Set<Account>()
            .AnyAsync(a => a.Email.ToLower() == request.Email.ToLower());
        if (existingEmail)
            return (false, null, "Email already registered");

        // Create account
        var account = new Account
        {
            Username = request.Username,
            Email = request.Email,
            PasswordHash = HashPassword(request.Password),
            CreatedAt = DateTime.UtcNow,
            IsActive = true,
            EmailVerified = false,
            EmailVerificationToken = GenerateToken()
        };

        // Create associated game user
        var user = new User
        {
            SynergyId = Guid.NewGuid().ToString(),
            Nickname = request.Username,
            Gold = 100000, // Starting gold for community server
            Cash = 500000, // Starting cash
            Level = 1,
            Experience = 0,
            Reputation = 0,
            CreatedAt = DateTime.UtcNow
        };

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

        account.UserId = user.Id;
        _context.Set<Account>().Add(account);
        await _context.SaveChangesAsync();

        _logger.LogInformation("New account registered: {Username} ({Email})", account.Username, account.Email);

        // Generate JWT token
        var token = GenerateJwtToken(account);
        return (true, token, null);
    }

    public async Task<(bool Success, LoginResponse? Response, string? Error)> LoginAsync(LoginRequest request)
    {
        // Find account by username or email
        var account = await _context.Set<Account>()
            .Include(a => a.User)
            .FirstOrDefaultAsync(a =>
                a.Username.ToLower() == request.UsernameOrEmail.ToLower() ||
                a.Email.ToLower() == request.UsernameOrEmail.ToLower());

        if (account == null)
            return (false, null, "Invalid username/email or password");

        if (!account.IsActive)
            return (false, null, "Account is disabled");

        // Verify password
        if (!VerifyPassword(request.Password, account.PasswordHash))
            return (false, null, "Invalid username/email or password");

        // Update last login
        account.LastLoginAt = DateTime.UtcNow;

        // Link device if provided
        if (!string.IsNullOrEmpty(request.DeviceId))
        {
            var deviceLink = await _context.Set<DeviceAccount>()
                .FirstOrDefaultAsync(da => da.AccountId == account.Id && da.DeviceId == request.DeviceId);

            if (deviceLink == null)
            {
                deviceLink = new DeviceAccount
                {
                    AccountId = account.Id,
                    DeviceId = request.DeviceId,
                    LinkedAt = DateTime.UtcNow,
                    LastUsedAt = DateTime.UtcNow
                };
                _context.Set<DeviceAccount>().Add(deviceLink);
            }
            else
            {
                deviceLink.LastUsedAt = DateTime.UtcNow;
            }
        }

        await _context.SaveChangesAsync();

        _logger.LogInformation("User logged in: {Username}", account.Username);

        // Generate JWT token
        var token = GenerateJwtToken(account);
        var expiresAt = DateTime.UtcNow.AddDays(30);

        var response = new LoginResponse
        {
            Token = token,
            AccountId = account.Id,
            Username = account.Username,
            Email = account.Email,
            ExpiresAt = expiresAt
        };

        return (true, response, null);
    }

    public async Task<(bool Success, string? Error)> ChangePasswordAsync(int accountId, ChangePasswordRequest request)
    {
        var account = await _context.Set<Account>().FindAsync(accountId);
        if (account == null)
            return (false, "Account not found");

        if (!VerifyPassword(request.CurrentPassword, account.PasswordHash))
            return (false, "Current password is incorrect");

        if (request.NewPassword.Length < 6)
            return (false, "New password must be at least 6 characters");

        if (request.NewPassword != request.ConfirmPassword)
            return (false, "Passwords do not match");

        account.PasswordHash = HashPassword(request.NewPassword);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Password changed for account: {AccountId}", accountId);
        return (true, null);
    }

    public async Task<(bool Success, string? Error)> ForgotPasswordAsync(ForgotPasswordRequest request)
    {
        var account = await _context.Set<Account>()
            .FirstOrDefaultAsync(a => a.Email.ToLower() == request.Email.ToLower());

        if (account == null)
        {
            // Don't reveal if email exists
            _logger.LogWarning("Password reset requested for non-existent email: {Email}", request.Email);
            return (true, null);
        }

        account.PasswordResetToken = GenerateToken();
        account.PasswordResetExpiry = DateTime.UtcNow.AddHours(24);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Password reset token generated for: {Email}", request.Email);

        // TODO: Send email with reset link
        // For now, just log the token
        _logger.LogWarning("Password reset token: {Token} (implement email service)", account.PasswordResetToken);

        return (true, null);
    }

    public async Task<(bool Success, string? Error)> ResetPasswordAsync(ResetPasswordRequest request)
    {
        var account = await _context.Set<Account>()
            .FirstOrDefaultAsync(a => a.PasswordResetToken == request.Token);

        if (account == null || account.PasswordResetExpiry == null || account.PasswordResetExpiry < DateTime.UtcNow)
            return (false, "Invalid or expired reset token");

        if (request.NewPassword.Length < 6)
            return (false, "Password must be at least 6 characters");

        if (request.NewPassword != request.ConfirmPassword)
            return (false, "Passwords do not match");

        account.PasswordHash = HashPassword(request.NewPassword);
        account.PasswordResetToken = null;
        account.PasswordResetExpiry = null;
        await _context.SaveChangesAsync();

        _logger.LogInformation("Password reset completed for account: {AccountId}", account.Id);
        return (true, null);
    }

    public async Task<(bool Success, string? Error)> LinkDeviceAsync(int accountId, LinkDeviceRequest request)
    {
        var account = await _context.Set<Account>().FindAsync(accountId);
        if (account == null)
            return (false, "Account not found");

        var existingLink = await _context.Set<DeviceAccount>()
            .FirstOrDefaultAsync(da => da.AccountId == accountId && da.DeviceId == request.DeviceId);

        if (existingLink != null)
            return (false, "Device already linked");

        var deviceLink = new DeviceAccount
        {
            AccountId = accountId,
            DeviceId = request.DeviceId,
            DeviceName = request.DeviceName,
            LinkedAt = DateTime.UtcNow,
            LastUsedAt = DateTime.UtcNow
        };

        _context.Set<DeviceAccount>().Add(deviceLink);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Device {DeviceId} linked to account {AccountId}", request.DeviceId, accountId);
        return (true, null);
    }

    public async Task<(bool Success, string? Error)> UnlinkDeviceAsync(int accountId, string deviceId)
    {
        var deviceLink = await _context.Set<DeviceAccount>()
            .FirstOrDefaultAsync(da => da.AccountId == accountId && da.DeviceId == deviceId);

        if (deviceLink == null)
            return (false, "Device not linked to this account");

        _context.Set<DeviceAccount>().Remove(deviceLink);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Device {DeviceId} unlinked from account {AccountId}", deviceId, accountId);
        return (true, null);
    }

    public async Task<AccountSettingsResponse?> GetAccountSettingsAsync(int accountId)
    {
        var account = await _context.Set<Account>()
            .Include(a => a.User)
            .Include(a => a.LinkedDevices)
            .FirstOrDefaultAsync(a => a.Id == accountId);

        if (account == null)
            return null;

        var carsOwned = account.UserId.HasValue
            ? await _context.OwnedCars.CountAsync(c => c.UserId == account.UserId.Value)
            : 0;

        return new AccountSettingsResponse
        {
            AccountId = account.Id,
            Username = account.Username,
            Email = account.Email,
            EmailVerified = account.EmailVerified,
            CreatedAt = account.CreatedAt,
            LastLoginAt = account.LastLoginAt,
            LinkedDevices = account.LinkedDevices.Select(d => new LinkedDeviceInfo
            {
                DeviceId = d.DeviceId,
                DeviceName = d.DeviceName,
                LinkedAt = d.LinkedAt,
                LastUsedAt = d.LastUsedAt
            }).ToList(),
            Gold = account.User?.Gold ?? 0,
            Cash = account.User?.Cash ?? 0,
            Level = account.User?.Level ?? 1,
            CarsOwned = carsOwned
        };
    }

    public async Task<Account?> ValidateTokenAsync(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"] ?? "RR3CommunityServer_DefaultSecret_ChangeThis");

            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            var jwtToken = (JwtSecurityToken)validatedToken;
            var accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

            return await _context.Set<Account>().FindAsync(accountId);
        }
        catch
        {
            return null;
        }
    }

    private string HashPassword(string password)
    {
        // Use BCrypt for password hashing
        return BCrypt.Net.BCrypt.HashPassword(password);
    }

    private bool VerifyPassword(string password, string hash)
    {
        return BCrypt.Net.BCrypt.Verify(password, hash);
    }

    private string GenerateJwtToken(Account account)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"] ?? "RR3CommunityServer_DefaultSecret_ChangeThis");

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim("id", account.Id.ToString()),
                new Claim("username", account.Username),
                new Claim("email", account.Email)
            }),
            Expires = DateTime.UtcNow.AddDays(30),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };

        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }

    private string GenerateToken()
    {
        return Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
    }
}
