using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Meezi.API.Models.Auth;
using Meezi.API.Services;
using Meezi.Core.Constants;
using Meezi.Shared;

namespace Meezi.API.Controllers;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IAuthService _authService;
    private readonly IValidator<SendOtpRequest> _sendOtpValidator;
    private readonly IValidator<VerifyOtpRequest> _verifyOtpValidator;
    private readonly IValidator<RefreshTokenRequest> _refreshValidator;
    private readonly IValidator<RegisterRequest> _registerValidator;
    private readonly IValidator<VerifyRegisterRequest> _verifyRegisterValidator;

    public AuthController(
        IAuthService authService,
        IValidator<SendOtpRequest> sendOtpValidator,
        IValidator<VerifyOtpRequest> verifyOtpValidator,
        IValidator<RefreshTokenRequest> refreshValidator,
        IValidator<RegisterRequest> registerValidator,
        IValidator<VerifyRegisterRequest> verifyRegisterValidator)
    {
        _authService = authService;
        _sendOtpValidator = sendOtpValidator;
        _verifyOtpValidator = verifyOtpValidator;
        _refreshValidator = refreshValidator;
        _registerValidator = registerValidator;
        _verifyRegisterValidator = verifyRegisterValidator;
    }

    [HttpPost("login")]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> LoginWithPassword(
        [FromBody] LoginWithPasswordRequest request,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
            return BadRequest(ValidationError("Username and password are required."));

        var (success, data, code, message, choices) = await _authService.LoginWithPasswordAsync(request, cancellationToken);

        if (!success && code == "CHOOSE_CAFE")
            return Ok(new ApiResponse<CafeChoicesResponse>(false, choices, new ApiError("CHOOSE_CAFE", "Please select a café to continue.")));

        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    [HttpPost("send-otp")]
    [EnableRateLimiting("auth-otp")]
    [ProducesResponseType(typeof(ApiResponse<SendOtpResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> SendOtp([FromBody] SendOtpRequest request, CancellationToken cancellationToken)
    {
        var validation = await _sendOtpValidator.ValidateAsync(request, cancellationToken);
        if (!validation.IsValid)
            return BadRequest(ValidationError(validation));

        var (success, data, code, message) = await _authService.SendOtpAsync(request, cancellationToken);
        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<SendOtpResponse>(true, data));
    }

    [HttpPost("verify-otp")]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> VerifyOtp([FromBody] VerifyOtpRequest request, CancellationToken cancellationToken)
    {
        var validation = await _verifyOtpValidator.ValidateAsync(request, cancellationToken);
        if (!validation.IsValid)
            return BadRequest(ValidationError(validation));

        var (success, data, code, message, choices) = await _authService.VerifyOtpAsync(request, cancellationToken);

        if (!success && code == "CHOOSE_CAFE")
            return Ok(new ApiResponse<CafeChoicesResponse>(false, choices, new ApiError("CHOOSE_CAFE", "Please select a café to continue.")));

        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    [HttpPost("switch-cafe")]
    [Authorize]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> SwitchCafe([FromBody] SwitchCafeRequest request, CancellationToken cancellationToken)
    {
        var userId = User.FindFirstValue(JwtRegisteredClaimNames.Sub)
            ?? User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId))
            return Unauthorized();

        var (success, data, code, message) = await _authService.SwitchCafeAsync(userId, request.CafeId, cancellationToken);
        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    [HttpPost("switch-branch")]
    [Authorize]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> SwitchBranch([FromBody] SwitchBranchRequest request, CancellationToken cancellationToken)
    {
        var userId = User.FindFirstValue(JwtRegisteredClaimNames.Sub)
            ?? User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId))
            return Unauthorized();

        var cafeId = User.FindFirstValue(MeeziClaimTypes.CafeId);
        if (string.IsNullOrEmpty(cafeId))
            return Unauthorized();

        var (success, data, code, message) = await _authService.SwitchBranchAsync(userId, cafeId, request.BranchId, cancellationToken);
        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    [HttpPost("refresh")]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> Refresh([FromBody] RefreshTokenRequest request, CancellationToken cancellationToken)
    {
        var validation = await _refreshValidator.ValidateAsync(request, cancellationToken);
        if (!validation.IsValid)
            return BadRequest(ValidationError(validation));

        var (success, data, code, message) = await _authService.RefreshAsync(request, cancellationToken);
        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    [HttpPost("register")]
    [EnableRateLimiting("auth-otp")]
    [ProducesResponseType(typeof(ApiResponse<SendOtpResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> Register([FromBody] RegisterRequest request, CancellationToken cancellationToken)
    {
        var validation = await _registerValidator.ValidateAsync(request, cancellationToken);
        if (!validation.IsValid)
            return BadRequest(ValidationError(validation));

        var (success, data, code, message) = await _authService.RegisterAsync(request, cancellationToken);
        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<SendOtpResponse>(true, data));
    }

    [HttpPost("verify-register")]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    public async Task<IActionResult> VerifyRegister([FromBody] VerifyRegisterRequest request, CancellationToken cancellationToken)
    {
        var validation = await _verifyRegisterValidator.ValidateAsync(request, cancellationToken);
        if (!validation.IsValid)
            return BadRequest(ValidationError(validation));

        var (success, data, code, message) = await _authService.VerifyRegisterAsync(request, cancellationToken);
        if (!success)
            return ErrorResult(code!, message!);

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    [HttpGet("me")]
    [Authorize]
    [ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public IActionResult GetMe()
    {
        var userId = User.FindFirstValue(JwtRegisteredClaimNames.Sub)
            ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
            ?? string.Empty;

        var expClaim = User.FindFirstValue(JwtRegisteredClaimNames.Exp);
        var expiresAt = expClaim != null && long.TryParse(expClaim, out var exp)
            ? DateTimeOffset.FromUnixTimeSeconds(exp).UtcDateTime
            : DateTime.UtcNow;

        var data = new AuthTokenResponse(
            AccessToken: string.Empty,
            RefreshToken: string.Empty,
            ExpiresAt: expiresAt,
            UserId: userId,
            CafeId: User.FindFirstValue(MeeziClaimTypes.CafeId) ?? string.Empty,
            Role: User.FindFirstValue(MeeziClaimTypes.Role) ?? string.Empty,
            PlanTier: User.FindFirstValue(MeeziClaimTypes.PlanTier) ?? string.Empty,
            Language: User.FindFirstValue(MeeziClaimTypes.Language) ?? string.Empty,
            Actor: User.FindFirstValue(MeeziClaimTypes.Actor) ?? MeeziActorKinds.Merchant,
            BranchId: User.FindFirstValue(MeeziClaimTypes.BranchId));

        return Ok(new ApiResponse<AuthTokenResponse>(true, data));
    }

    private static ApiResponse<object> ValidationError(FluentValidation.Results.ValidationResult validation)
    {
        var first = validation.Errors.First();
        return new ApiResponse<object>(false, null, new ApiError("VALIDATION_ERROR", first.ErrorMessage, first.PropertyName));
    }

    private static ApiResponse<object> ValidationError(string message) =>
        new(false, null, new ApiError("VALIDATION_ERROR", message));

    private IActionResult ErrorResult(string code, string message) => code switch
    {
        "RATE_LIMITED" => StatusCode(StatusCodes.Status429TooManyRequests,
            new ApiResponse<object>(false, null, new ApiError(code, message))),
        "NOT_FOUND" => NotFound(new ApiResponse<object>(false, null, new ApiError(code, message))),
        "INVALID_OTP" or "INVALID_TOKEN" => Unauthorized(new ApiResponse<object>(false, null, new ApiError(code, message))),
        "BRANCH_FORBIDDEN" => StatusCode(StatusCodes.Status403Forbidden,
            new ApiResponse<object>(false, null, new ApiError(code, message))),
        "ALREADY_REGISTERED" => Conflict(new ApiResponse<object>(false, null, new ApiError(code, message))),
        _ => BadRequest(new ApiResponse<object>(false, null, new ApiError(code, message)))
    };
}
