using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Nexus.Api.DTOs;
using Nexus.Api.Integrations;
using Nexus.Api.Http;
using Nexus.Api.RateLimiting;
using Nexus.Api.Security;
using Nexus.Api.Services;

namespace Nexus.Api.Controllers;

[ApiController]
[Route("api/v1/auth")]
public class AuthController(
    IAuthService authService,
    IConfiguration config,
    IHostEnvironment env,
    LoginAttemptTracker attemptTracker) : ControllerBase
{
    [HttpPost("login")]
    [AllowAnonymous]
    [EnableRateLimiting("auth")]
    public async Task<IResult> Login([FromBody] LoginRequest request, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.Password))
            return Results.ValidationProblem(new Dictionary<string, string[]> { ["credentials"] = ["Email and password are required."] });

        var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";

        var session = await authService.LoginAsync(request, ct);
        if (session is null)
        {
            var remaining = attemptTracker.RecordFailedAttempt(ip);
            var retryAfterSeconds = attemptTracker.GetRetryAfterSeconds(ip);

            // Attach remaining info to the 401 response via headers only
            // (the frontend can also parse the 429 body)
            HttpContext.Response.Headers["X-RateLimit-Remaining"] = remaining.ToString();
            HttpContext.Response.Headers["X-RateLimit-Limit"] = "5";
            if (retryAfterSeconds > 0)
                HttpContext.Response.Headers["X-RateLimit-Reset"] =
                    DateTimeOffset.UtcNow.AddSeconds(retryAfterSeconds).ToUnixTimeSeconds().ToString();

            return Results.Problem(
                statusCode: StatusCodes.Status401Unauthorized,
                title: "Authentication failed",
                detail: "Invalid email or password.",
                extensions: new Dictionary<string, object?>
                {
                    ["code"] = NexusProblemCodes.Unauthenticated,
                    ["remaining"] = remaining,
                    ["retryAfterSeconds"] = retryAfterSeconds
                });
        }

        // Success — reset attempt counter
        attemptTracker.Reset(ip);
        SetRefreshCookie(Response, session.RefreshToken);
        Response.Headers.CacheControl = "no-store";
        Response.Headers["X-RateLimit-Remaining"] = "5";
        Response.Headers["X-RateLimit-Limit"] = "5";
        return Results.Ok(ToAuthResponse(session));
    }

    [HttpPost("refresh")]
    [AllowAnonymous]
    [EnableRateLimiting("auth")]
    public async Task<IResult> Refresh(CancellationToken ct)
    {
        if (!BrowserRequestOriginGuard.IsAllowed(Request))
            return CrossSiteRequestRejected();

        if (!Request.Cookies.TryGetValue("nexus_refresh", out var refreshToken))
            return Unauthenticated("No active refresh session was provided.");

        var session = await authService.RefreshAsync(refreshToken!, ct);
        if (session is null)
        {
            ClearRefreshCookie(Response);
            return Unauthenticated("The refresh session is invalid or expired.");
        }

        SetRefreshCookie(Response, session.RefreshToken);
        Response.Headers.CacheControl = "no-store";
        Response.Headers["X-RateLimit-Remaining"] = "5";
        Response.Headers["X-RateLimit-Limit"] = "5";
        return Results.Ok(ToAuthResponse(session));
    }

    [HttpPost("logout")]
    [AllowAnonymous]
    public async Task<IResult> Logout(CancellationToken ct)
    {
        if (!BrowserRequestOriginGuard.IsAllowed(Request))
            return CrossSiteRequestRejected();

        if (Request.Cookies.TryGetValue("nexus_refresh", out var refreshToken))
            await authService.RevokeAsync(refreshToken!, ct);

        ClearRefreshCookie(Response);
        return Results.NoContent();
    }

    [HttpGet("me")]
    public async Task<IResult> GetMe(CancellationToken ct)
    {
        var subject = User.FindFirst(System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames.Sub)?.Value;
        if (!Guid.TryParse(subject, out var userId)) return Results.Unauthorized();

        var user = await authService.GetUserAsync(userId, ct);
        return user is null
            ? Results.Unauthorized()
            : Results.Ok(new UserInfo { Id = user.Id, Email = user.Email, DisplayName = user.DisplayName, Role = user.Role });
    }

    [HttpPatch("profile")]
    public async Task<IResult> UpdateProfile([FromBody] UpdateProfileRequest request, CancellationToken ct)
    {
        var subject = User.FindFirst(System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames.Sub)?.Value;
        if (!Guid.TryParse(subject, out var userId)) return Results.Unauthorized();

        var user = await authService.UpdateProfileAsync(userId, request, ct);
        return user is null
            ? Results.NotFound()
            : Results.Ok(new UserInfo { Id = user.Id, Email = user.Email, DisplayName = user.DisplayName, Role = user.Role });
    }

    [HttpPost("admin-reset-password")]
    [AllowAnonymous]
    [EnableRateLimiting("agents")]
    public async Task<IResult> AdminResetPassword([FromBody] AdminResetPasswordRequest request, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.NewPassword) || string.IsNullOrWhiteSpace(request.AdminToken))
            return Results.ValidationProblem(new Dictionary<string, string[]> { ["request"] = ["Email, new password, and admin token are required."] });

        if (request.NewPassword.Length < 10)
            return Results.ValidationProblem(new Dictionary<string, string[]> { ["newPassword"] = ["New password must be at least 10 characters."] });

        var success = await authService.AdminResetPasswordAsync(request.Email, request.NewPassword, request.AdminToken, ct);
        if (!success)
            return Results.Problem("Password reset failed. Check the admin token, email, and that the user exists.", statusCode: 400);

        return Results.Ok(new { message = "Password reset successfully." });
    }

    [HttpPost("change-password")]
    public async Task<IResult> ChangePassword([FromBody] ChangePasswordRequest request, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.CurrentPassword) || string.IsNullOrWhiteSpace(request.NewPassword))
            return Results.ValidationProblem(new Dictionary<string, string[]> { ["password"] = ["Current and new passwords are required."] });

        if (request.NewPassword.Length < 10)
            return Results.ValidationProblem(new Dictionary<string, string[]> { ["newPassword"] = ["New password must be at least 10 characters."] });

        var subject = User.FindFirst(System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames.Sub)?.Value;
        if (!Guid.TryParse(subject, out var userId)) return Results.Unauthorized();

        var success = await authService.ChangePasswordAsync(userId, request, ct);
        return success ? Results.Ok(new { message = "Password changed successfully." }) : Results.Problem("Current password is incorrect.", statusCode: 400);
    }

    private static AuthResponse ToAuthResponse(AuthSession session) => new()
    {
        AccessToken = session.AccessToken,
        ExpiresAt = session.ExpiresAt,
        User = session.User
    };

    private static IResult Unauthenticated(string detail)
        => Results.Problem(
            statusCode: StatusCodes.Status401Unauthorized,
            title: "Authentication required",
            detail: detail,
            extensions: new Dictionary<string, object?>
            {
                ["code"] = NexusProblemCodes.Unauthenticated
            });

    private static IResult CrossSiteRequestRejected()
        => Results.Problem(
            statusCode: StatusCodes.Status403Forbidden,
            title: "Cross-site request rejected",
            detail: "Refresh and logout requests must originate from the Nexus origin.",
            extensions: new Dictionary<string, object?>
            {
                ["code"] = NexusProblemCodes.Forbidden
            });

    private void SetRefreshCookie(HttpResponse response, string token)
    {
        var days = config.GetValue<int?>("Jwt:RefreshTokenExpirationDays") ?? 7;
        response.Cookies.Append("nexus_refresh", token, new CookieOptions
        {
            HttpOnly = true,
            Secure = !env.IsDevelopment(),
            SameSite = SameSiteMode.Strict,
            Path = "/api/v1/auth",
            MaxAge = TimeSpan.FromDays(days),
            IsEssential = true
        });
    }

    private void ClearRefreshCookie(HttpResponse response)
    {
        response.Cookies.Delete("nexus_refresh", new CookieOptions
        {
            HttpOnly = true,
            Secure = !env.IsDevelopment(),
            SameSite = SameSiteMode.Strict,
            Path = "/api/v1/auth"
        });
    }
}
