using GestionaDenuncias.Shared.Models;
using ApiDenuncias.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ApiDenuncias.Controllers;

[ApiController]
[Authorize]
[Route("api/inbox")]
public sealed class InboxController : ControllerBase
{
    private static readonly TimeSpan[] ExportRetryDelays =
    [
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10)
    ];

    private readonly GlobalLeaksSessionStore _sessionStore;
    private readonly PendingGlobalLeaksLoginStore _pendingLoginStore;
    private readonly GlobalLeaksClient _globalLeaksClient;
    private readonly DenunciaInboxService _inboxService;
    private readonly IInboxTrackingService _trackingService;
    private readonly ILogger<InboxController> _logger;

    public InboxController(
        GlobalLeaksSessionStore sessionStore,
        PendingGlobalLeaksLoginStore pendingLoginStore,
        GlobalLeaksClient globalLeaksClient,
        DenunciaInboxService inboxService,
        IInboxTrackingService trackingService,
        ILogger<InboxController> logger)
    {
        _sessionStore = sessionStore;
        _pendingLoginStore = pendingLoginStore;
        _globalLeaksClient = globalLeaksClient;
        _inboxService = inboxService;
        _trackingService = trackingService;
        _logger = logger;
    }

    [HttpGet("session")]
    public async Task<ActionResult<ApiGlobalLeaksSessionDto?>> GetSession(CancellationToken cancellationToken)
    {
        var session = await _sessionStore.GetAsync(GetUsername(), cancellationToken);
        return Ok(ToDto(session));
    }

    [HttpPost("session/renew/prepare")]
    public async Task<ActionResult<ApiLoginPrepareResponse>> PrepareRenewSession(CancellationToken cancellationToken)
    {
        var username = GetUsername();
        var current = await _sessionStore.GetAsync(username, cancellationToken);
        if (current is null || string.IsNullOrWhiteSpace(current.Password))
        {
            return BadRequest(new ApiError("No hay credenciales guardadas para este usuario. Cierra sesion y vuelve a entrar."));
        }

        try
        {
            var prepared = await _globalLeaksClient.PrepareLoginAsync(
                current.Username,
                current.Password,
                cancellationToken);

            var pending = _pendingLoginStore.Create(
                prepared.Username,
                current.Password,
                prepared.FinalPassword,
                prepared.TokenAnswer);

            return Ok(new ApiLoginPrepareResponse(pending.Id, pending.Username, pending.ExpiresAtUtc));
        }
        catch (GlobalLeaksValidationException ex)
        {
            return StatusCode(ex.StatusCode, new ApiError(ex.Message));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "No se ha podido preparar la renovacion GlobalLeaks para {Username}.", username);
            return StatusCode(
                StatusCodes.Status500InternalServerError,
                new ApiError("No se ha podido preparar la renovacion de GlobalLeaks. Intentalo de nuevo en unos segundos."));
        }
    }

    [HttpPost("session/renew")]
    public async Task<ActionResult<ApiGlobalLeaksSessionDto>> RenewSession(
        RenewGlobalLeaksSessionRequest request,
        CancellationToken cancellationToken)
    {
        var username = GetUsername();
        var current = await _sessionStore.GetAsync(username, cancellationToken);
        if (current is null || string.IsNullOrWhiteSpace(current.Password))
        {
            return BadRequest(new ApiError("No hay credenciales guardadas para este usuario. Cierra sesion y vuelve a entrar."));
        }

        if (string.IsNullOrWhiteSpace(request.Authcode) || request.Authcode.Trim().Length != 6)
        {
            return BadRequest(new ApiError("Introduce un codigo 2FA valido de 6 digitos."));
        }

        try
        {
            GlSession session;
            if (!string.IsNullOrWhiteSpace(request.PendingLoginId))
            {
                var pending = _pendingLoginStore.Get(request.PendingLoginId);
                if (!string.Equals(pending.Username, current.Username, StringComparison.OrdinalIgnoreCase))
                {
                    return BadRequest(new ApiError("La preparacion del login no corresponde al usuario actual."));
                }

                session = await _globalLeaksClient.CompleteLoginAsync(
                    pending.Username,
                    pending.FinalPassword,
                    pending.TokenAnswer,
                    request.Authcode.Trim(),
                    cancellationToken);

                _pendingLoginStore.Remove(pending.Id);
            }
            else
            {
                session = await _globalLeaksClient.LoginAsync(
                    current.Username,
                    current.Password,
                    request.Authcode.Trim(),
                    cancellationToken);
            }

            await _sessionStore.UpdateSessionAsync(username, session.Id, session.Role, cancellationToken);
            var stored = await _sessionStore.GetAsync(username, cancellationToken);
            return Ok(ToDto(stored));
        }
        catch (GlobalLeaksValidationException ex)
        {
            return StatusCode(ex.StatusCode, new ApiError(ex.Message));
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new ApiError(ex.Message));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "No se ha podido cargar la bandeja GlobalLeaks para {Username}.", username);
            return StatusCode(
                StatusCodes.Status500InternalServerError,
                new ApiError("No se ha podido renovar la sesion de GlobalLeaks. Intentalo de nuevo en unos segundos."));
        }
    }

    [HttpPost("session/clear")]
    public async Task<IActionResult> ClearSession(CancellationToken cancellationToken)
    {
        await _sessionStore.ClearSessionAsync(GetUsername(), cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpGet("reports")]
    public async Task<ActionResult<InboxSnapshotResponse>> GetReports(CancellationToken cancellationToken)
    {
        var username = GetUsername();
        var session = await RequireActiveSessionAsync(username, cancellationToken);
        if (session is null)
        {
            return Unauthorized(new ApiError("La sesion de GlobalLeaks ha caducado. Renueva el 2FA.", SessionExpired: true));
        }

        try
        {
            var state = await _trackingService.GetUserStateAsync(username, cancellationToken);
            var contexts = await _globalLeaksClient.GetContextsAsync(session.SessionId!, cancellationToken);
            var reports = await _globalLeaksClient.GetReportsAsync(session.SessionId!, "all", null, null, cancellationToken, contexts);
            var enrichedReports = await _trackingService.RegisterSnapshotAsync(username, reports, cancellationToken);
            var activityReports = await _globalLeaksClient.EnrichReportsWithActivityAsync(
                session.SessionId!,
                enrichedReports,
                state.LastDownloadedReportMomentUtc,
                cancellationToken);

            return Ok(new InboxSnapshotResponse(contexts, activityReports, state));
        }
        catch (GlobalLeaksSessionExpiredException)
        {
            await _sessionStore.ClearSessionAsync(username, cancellationToken);
            return Unauthorized(new ApiError("La sesion de GlobalLeaks ha caducado. Renueva el 2FA.", SessionExpired: true));
        }
        catch (GlobalLeaksValidationException ex)
        {
            return ToGlobalLeaksApiError(ex, "cargar la bandeja de GlobalLeaks");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "No se ha podido cargar la bandeja GlobalLeaks para {Username}.", username);
            return StatusCode(
                StatusCodes.Status500InternalServerError,
                new ApiError("No se ha podido cargar la bandeja de GlobalLeaks. Intentalo de nuevo en unos segundos."));
        }
    }

    [HttpPost("reports/{reportId}/import")]
    public async Task<ActionResult<ImportSummary>> ImportReport(
        string reportId,
        ImportReportRequest request,
        CancellationToken cancellationToken)
    {
        var username = GetUsername();
        var session = await RequireActiveSessionAsync(username, cancellationToken);
        if (session is null)
        {
            return Unauthorized(new ApiError("La sesion de GlobalLeaks ha caducado. Renueva el 2FA.", SessionExpired: true));
        }

        var report = string.IsNullOrWhiteSpace(request.Report.Id)
            ? request.Report with { Id = reportId }
            : request.Report;

        try
        {
            await _trackingService.EnsureReportCanBeImportedByUserAsync(username, report, cancellationToken);

            ReportDetailDto? reportDetail = null;
            try
            {
                reportDetail = await _globalLeaksClient.GetReportDetailAsync(
                    session.SessionId!,
                    report.Id,
                    report.LastAccess,
                    cancellationToken);
            }
            catch (GlobalLeaksValidationException ex) when (ex.StatusCode == StatusCodes.Status422UnprocessableEntity ||
                                                            ex.StatusCode is >= 500 and <= 504)
            {
                _logger.LogWarning(
                    ex,
                    "No se ha podido obtener el detalle de fechas de adjuntos para la denuncia {ReportId}. Se usara la fecha del paquete exportado.",
                    report.Id);
            }

            var reportPackage = await DownloadReportPackageWithRetryAsync(session.SessionId!, report, cancellationToken);

            FileDownloadResult? json = null;
            try
            {
                json = await _globalLeaksClient.ExportReportJsonAsync(session.SessionId!, report.Id, cancellationToken);
            }
            catch (GlobalLeaksValidationException ex) when (ex.StatusCode == 422)
            {
                json = null;
            }

            var result = await _inboxService.ImportFromGlobalLeaksAsync(reportPackage, json, reportDetail, cancellationToken);
            if (result.ImportedCount > 0)
            {
                await _trackingService.MarkReportImportedAsync(
                    username,
                    report,
                    result.ImportedComplaintIds?.FirstOrDefault(),
                    cancellationToken);
            }

            return Ok(result);
        }
        catch (GlobalLeaksSessionExpiredException)
        {
            await _sessionStore.ClearSessionAsync(username, cancellationToken);
            return Unauthorized(new ApiError("La sesion de GlobalLeaks ha caducado durante la importacion. Renueva el 2FA.", SessionExpired: true));
        }
        catch (GlobalLeaksValidationException ex)
        {
            return ToGlobalLeaksApiError(ex, "importar la denuncia");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "No se ha podido importar la denuncia {ReportId} para {Username}.", reportId, username);
            return StatusCode(
                StatusCodes.Status500InternalServerError,
                new ApiError("No se ha podido importar la denuncia. Intentalo de nuevo en unos segundos."));
        }
    }

    [HttpGet("reports/{reportId}/detail")]
    public async Task<ActionResult<ReportDetailDto>> GetReportDetail(
        string reportId,
        [FromQuery] string? lastAccess,
        CancellationToken cancellationToken)
    {
        var username = GetUsername();
        var session = await RequireActiveSessionAsync(username, cancellationToken);
        if (session is null)
        {
            return Unauthorized(new ApiError("La sesion de GlobalLeaks ha caducado. Renueva el 2FA.", SessionExpired: true));
        }

        try
        {
            return Ok(await _globalLeaksClient.GetReportDetailAsync(session.SessionId!, reportId, lastAccess, cancellationToken));
        }
        catch (GlobalLeaksSessionExpiredException)
        {
            await _sessionStore.ClearSessionAsync(username, cancellationToken);
            return Unauthorized(new ApiError("La sesion de GlobalLeaks ha caducado. Renueva el 2FA.", SessionExpired: true));
        }
        catch (GlobalLeaksValidationException ex)
        {
            return ToGlobalLeaksApiError(ex, "leer el detalle de la denuncia");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "No se ha podido leer el detalle de la denuncia {ReportId} para {Username}.", reportId, username);
            return StatusCode(
                StatusCodes.Status500InternalServerError,
                new ApiError("No se ha podido abrir el detalle de la denuncia. Intentalo de nuevo en unos segundos."));
        }
    }

    private async Task<FileDownloadResult> DownloadReportPackageWithRetryAsync(
        string sessionId,
        ReportDto report,
        CancellationToken cancellationToken)
    {
        for (var attempt = 0; ; attempt++)
        {
            try
            {
                return await _globalLeaksClient.DownloadReportPackageAsync(sessionId, report.Id, cancellationToken);
            }
            catch (GlobalLeaksValidationException ex) when (IsExportNotReady(ex) && attempt < ExportRetryDelays.Length)
            {
                var delay = ExportRetryDelays[attempt];
                _logger.LogWarning(
                    ex,
                    "GlobalLeaks aun no ha preparado la exportacion de la denuncia {ReportId}. Reintento {Attempt}/{Total} en {DelaySeconds}s.",
                    report.Id,
                    attempt + 1,
                    ExportRetryDelays.Length,
                    delay.TotalSeconds);

                await Task.Delay(delay, cancellationToken);
            }
            catch (GlobalLeaksValidationException ex) when (IsExportNotReady(ex))
            {
                throw new GlobalLeaksValidationException(
                    "GlobalLeaks todavia esta preparando la exportacion de esta denuncia. Espera unos segundos y vuelve a importarla.",
                    StatusCodes.Status503ServiceUnavailable);
            }
        }
    }

    private static bool IsExportNotReady(GlobalLeaksValidationException ex)
        => ex.StatusCode is >= 500 and <= 504;

    private static ActionResult ToGlobalLeaksApiError(GlobalLeaksValidationException ex, string operation)
    {
        if (ex.StatusCode == StatusCodes.Status401Unauthorized ||
            ex.StatusCode == StatusCodes.Status403Forbidden)
        {
            return new ObjectResult(new ApiError(
                "No tienes permiso en GlobalLeaks para acceder a esa denuncia. Puede ser una denuncia anterior a la creacion de tu usuario; consulta con el administrador del buzon."))
            {
                StatusCode = StatusCodes.Status403Forbidden
            };
        }

        if (ex.StatusCode is >= 500 and <= 504)
        {
            return new ObjectResult(new ApiError(
                $"GlobalLeaks no ha podido {operation} en este momento. Intentalo de nuevo en unos segundos."))
            {
                StatusCode = StatusCodes.Status503ServiceUnavailable
            };
        }

        return new ObjectResult(new ApiError(ex.Message))
        {
            StatusCode = ex.StatusCode
        };
    }

    private async Task<GlobalLeaksStoredSession?> RequireActiveSessionAsync(string username, CancellationToken cancellationToken)
    {
        var session = await _sessionStore.GetAsync(username, cancellationToken);
        return session?.HasActiveSession == true ? session : null;
    }

    private string GetUsername()
        => User.Identity?.Name ?? throw new InvalidOperationException("No hay usuario autenticado.");

    private static ApiGlobalLeaksSessionDto? ToDto(GlobalLeaksStoredSession? session)
        => session is null
            ? null
            : new ApiGlobalLeaksSessionDto(session.Username, session.Role, session.HasActiveSession, session.UpdatedAt);



}
