using System.Security.Claims;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;

namespace JobTrackerApi.Controllers;

[ApiController]
[Route("api/account-lifecycle")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class AccountLifecycleController(
    JobTrackerContext db,
    AccountDeletionService deletions,
    TimeProvider timeProvider) : ControllerBase
{
    public sealed record DeleteAccountRequest(string Confirmation);

    [HttpGet("status")]
    public async Task<IActionResult> Status(CancellationToken cancellationToken)
    {
        var ownerUserId = LocalAuthIdentity.GetRequiredUserId(User);
        if (ownerUserId is null) return Unauthorized();
        var user = await db.Users.AsNoTracking().FirstOrDefaultAsync(item => item.Id == ownerUserId, cancellationToken);
        if (user is null) return NotFound();
        var request = await db.AccountDeletionRequests.AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderByDescending(item => item.Id).FirstOrDefaultAsync(cancellationToken);
        return Ok(new
        {
            deletionEnabled = deletions.CanAcceptRequests,
            deletionStatus = user.DeletionStatus,
            requiredConfirmation = $"DELETE {user.Email}",
            request = request is null ? null : ToDto(request),
        });
    }

    [HttpPost("delete")]
    [EnableRateLimiting("account-data")]
    public async Task<IActionResult> Delete([FromBody] DeleteAccountRequest request, CancellationToken cancellationToken)
    {
        if (!deletions.CanAcceptRequests)
            return Problem(statusCode: StatusCodes.Status503ServiceUnavailable, title: "Account deletion unavailable", detail: "Account deletion remains disabled until retention and restore safeguards are approved.");
        var ownerUserId = LocalAuthIdentity.GetRequiredUserId(User);
        if (ownerUserId is null) return Unauthorized();
        var user = await db.Users.AsNoTracking().FirstOrDefaultAsync(item => item.Id == ownerUserId, cancellationToken);
        if (user is null) return NotFound();
        if (!string.Equals(request.Confirmation?.Trim(), $"DELETE {user.Email}", StringComparison.Ordinal))
            return BadRequest("Type the exact confirmation shown before deleting the account.");
        if (!await IsRecentlyAuthenticated(ownerUserId, cancellationToken))
            return StatusCode(StatusCodes.Status403Forbidden, new ProblemDetails { Title = "Recent sign-in required", Detail = "Sign in again before deleting the account." });
        if (await IsFinalAdministrator(ownerUserId, cancellationToken))
            return Conflict(new ProblemDetails { Title = "Last administrator protected", Detail = "Assign the Admin role to another user before deleting the final administrator." });

        var result = await deletions.RequestAsync(ownerUserId, ownerUserId, cancellationToken);
        if (result is null) return StatusCode(StatusCodes.Status503ServiceUnavailable);
        ExpireCookies();
        return Accepted(new { requestId = result.RequestId, status = result.Status, stage = result.Stage });
    }

    [HttpGet("admin/requests/{id:guid}")]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> AdminStatus(Guid id, CancellationToken cancellationToken)
    {
        var request = await db.AccountDeletionRequests.AsNoTracking().FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
        return request is null ? NotFound() : Ok(ToDto(request));
    }

    private async Task<bool> IsRecentlyAuthenticated(string ownerUserId, CancellationToken cancellationToken)
    {
        var sid = User.FindFirstValue("sid");
        if (string.IsNullOrWhiteSpace(sid)) return false;
        var session = await db.UserSessions.IgnoreQueryFilters().AsNoTracking().FirstOrDefaultAsync(item => item.Id == sid && item.UserId == ownerUserId, cancellationToken);
        return session is { RevokedAtUtc: null } && session.CreatedAtUtc >= timeProvider.GetUtcNow().AddMinutes(-15);
    }

    private async Task<bool> IsFinalAdministrator(string ownerUserId, CancellationToken cancellationToken)
    {
        var adminRoleId = await db.Roles.Where(item => item.NormalizedName == "ADMIN").Select(item => item.Id).FirstOrDefaultAsync(cancellationToken);
        if (adminRoleId is null) return false;
        var isAdmin = await db.UserRoles.AnyAsync(item => item.UserId == ownerUserId && item.RoleId == adminRoleId, cancellationToken);
        return isAdmin && await db.UserRoles.CountAsync(item => item.RoleId == adminRoleId, cancellationToken) <= 1;
    }

    private void ExpireCookies()
    {
        Response.Cookies.Delete(AuthSessionOptions.SessionCookieName, AuthSessionOptions.BuildExpiredCookie(Request.IsHttps));
        Response.Cookies.Delete(AuthSessionOptions.CsrfCookieName, AuthSessionOptions.BuildExpiredReadableCookie(Request.IsHttps));
        TrustedDeviceService.ClearCookie(Response, Request.IsHttps);
    }

    private static object ToDto(AccountDeletionRequest request) => new
    {
        request.Id,
        request.Status,
        request.Stage,
        request.AttemptCount,
        request.DatabaseRowCount,
        request.FileCount,
        request.WarningJson,
        request.LastErrorCategory,
        request.LastErrorMessage,
        request.RequestedAtUtc,
        request.StartedAtUtc,
        request.CompletedAtUtc,
    };
}
