using FlatRender.IdentitySvc.Application.Services;
using FlatRender.IdentitySvc.Application.Services.Interfaces;
using FlatRender.IdentitySvc.Models.Requests;
using FlatRender.IdentitySvc.Models.Responses;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace FlatRender.IdentitySvc.Controllers;

[ApiController]
[Route("v1/auth")]
public class AuthController(IAuthService authService, OAuthService oauthService) : ControllerBase
{
    [HttpPost("register")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(RegisterResponse), 201)]
    public async Task<IActionResult> Register([FromBody] RegisterRequest request)
    {
        var result = await authService.RegisterAsync(request, GetClientIp());
        return StatusCode(201, result);
    }

    // ── Google OAuth ────────────────────────────────────────────────────────────
    [HttpGet("google/start")]
    [AllowAnonymous]
    public async Task<IActionResult> GoogleStart([FromQuery] string return_url)
    {
        try
        {
            var url = await oauthService.BuildGoogleAuthUrlAsync(return_url ?? "");
            return Redirect(url);
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = new { message = ex.Message } });
        }
    }

    [HttpGet("google/callback")]
    [AllowAnonymous]
    public async Task<IActionResult> GoogleCallback([FromQuery] string? code, [FromQuery] string? state, [FromQuery] string? error)
    {
        if (!string.IsNullOrEmpty(error) || string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state))
            return BadRequest(new { error = new { message = error ?? "Missing authorization code" } });
        try
        {
            var result = await oauthService.HandleGoogleCallbackAsync(code, state, GetClientIp());
            // Hand tokens back to the frontend via URL fragment (not sent to servers/logs).
            var sep = result.ReturnUrl.Contains('#') ? "&" : "#";
            var redirect = $"{result.ReturnUrl}{sep}access_token={Uri.EscapeDataString(result.Tokens.AccessToken)}" +
                           $"&refresh_token={Uri.EscapeDataString(result.Tokens.RefreshToken)}&expires_in={result.Tokens.ExpiresIn}";
            return Redirect(redirect);
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = new { message = ex.Message } });
        }
    }

    [HttpPost("login")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(AuthTokensResponse), 200)]
    [ProducesResponseType(401)]
    [ProducesResponseType(403)]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        try
        {
            var result = await authService.LoginAsync(request, GetClientIp());
            return Ok(result);
        }
        catch (MfaRequiredException mfaEx)
        {
            return StatusCode(403, new { mfa_required = true, mfa_token = mfaEx.MfaToken });
        }
    }

    [HttpPost("refresh")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(AuthTokensResponse), 200)]
    public async Task<IActionResult> Refresh([FromBody] RefreshTokenRequest request)
    {
        var result = await authService.RefreshAsync(request.RefreshToken);
        return Ok(result);
    }

    [HttpPost("logout")]
    [Authorize]
    [ProducesResponseType(204)]
    public async Task<IActionResult> Logout()
    {
        await authService.LogoutAsync(GetSessionId(), GetUserId());
        return NoContent();
    }

    [HttpGet("sessions")]
    [Authorize]
    [ProducesResponseType(typeof(object), 200)]
    public async Task<IActionResult> GetSessions()
    {
        var sessions = await authService.GetSessionsAsync(GetUserId());
        return Ok(new { sessions });
    }

    [HttpDelete("sessions/{sessionId:guid}")]
    [Authorize]
    [ProducesResponseType(204)]
    public async Task<IActionResult> RevokeSession(Guid sessionId)
    {
        await authService.RevokeSessionAsync(sessionId, GetUserId());
        return NoContent();
    }

    [HttpPost("verify/email")]
    [AllowAnonymous]
    public async Task<IActionResult> VerifyEmail([FromBody] VerifyOtpRequest request)
    {
        var ok = await authService.VerifyEmailAsync(request.Token, request.Code);
        return ok ? Ok() : BadRequest(new { error = "Invalid code" });
    }

    [HttpPost("verify/phone")]
    [AllowAnonymous]
    public async Task<IActionResult> VerifyPhone([FromBody] VerifyOtpRequest request)
    {
        var ok = await authService.VerifyPhoneAsync(request.Token, request.Code);
        return ok ? Ok() : BadRequest(new { error = "Invalid code" });
    }

    [HttpPost("password/reset/request")]
    [AllowAnonymous]
    [ProducesResponseType(202)]
    public async Task<IActionResult> PasswordResetRequest([FromBody] PasswordResetRequestDto request)
    {
        await authService.RequestPasswordResetAsync(request.TenantSlug, request.Email, request.PhoneNumber);
        return Accepted();
    }

    [HttpPost("password/reset/confirm")]
    [AllowAnonymous]
    public async Task<IActionResult> PasswordResetConfirm([FromBody] PasswordResetConfirmRequest request)
    {
        await authService.ConfirmPasswordResetAsync(request.Token, request.NewPassword);
        return Ok();
    }

    [HttpPost("password/change")]
    [Authorize]
    [ProducesResponseType(204)]
    public async Task<IActionResult> PasswordChange([FromBody] PasswordChangeRequest request)
    {
        await authService.ChangePasswordAsync(GetUserId(), request.CurrentPassword, request.NewPassword);
        return NoContent();
    }

    [HttpPost("mfa/setup")]
    [Authorize]
    [ProducesResponseType(typeof(MfaSetupResponse), 200)]
    public async Task<IActionResult> MfaSetup([FromBody] MfaSetupRequest request)
    {
        var result = await authService.SetupMfaAsync(GetUserId(), request.FactorType, request.Label);
        return Ok(result);
    }

    [HttpPost("mfa/verify")]
    [Authorize]
    public async Task<IActionResult> MfaVerify([FromBody] MfaVerifyRequest request)
    {
        var ok = await authService.VerifyMfaAsync(GetUserId(), request.FactorId, request.Code);
        return ok ? Ok() : BadRequest(new { error = "Invalid code" });
    }

    [HttpPost("mfa/challenge")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(AuthTokensResponse), 200)]
    public async Task<IActionResult> MfaChallenge([FromBody] MfaChallengeRequest request)
    {
        var result = await authService.ChallengeMfaAsync(request.MfaToken, request.Code);
        return Ok(result);
    }

    [HttpPost("push/subscribe")]
    [Authorize]
    [ProducesResponseType(201)]
    public async Task<IActionResult> PushSubscribe([FromBody] PushSubscribeRequest request)
    {
        await authService.SubscribePushAsync(
            GetUserId(), GetTenantId(),
            request.Endpoint, request.Keys.P256dh, request.Keys.Auth, request.UserAgent);
        return StatusCode(201);
    }

    [HttpPost("push/unsubscribe")]
    [Authorize]
    [ProducesResponseType(204)]
    public async Task<IActionResult> PushUnsubscribe([FromBody] PushUnsubscribeRequest request)
    {
        await authService.UnsubscribePushAsync(GetUserId(), request.Endpoint);
        return NoContent();
    }

    // ── Helpers ───────────────────────────────────────────────────────────

    private Guid GetUserId() => Guid.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value
        ?? User.FindFirst("sub")?.Value ?? throw new UnauthorizedAccessException());

    private Guid GetTenantId() => Guid.Parse(User.FindFirst("tenant_id")?.Value
        ?? throw new UnauthorizedAccessException());

    private Guid GetSessionId() => Guid.Parse(User.FindFirst("jti")?.Value ?? Guid.Empty.ToString());

    private string? GetClientIp() => HttpContext.Connection.RemoteIpAddress?.ToString();
}
