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 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: {ex.GetType().Name}: {ex.Message}"));
        }
    }

    [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 cargar la bandeja: {ex.GetType().Name}: {ex.Message}"));
        }
    }

    [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 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 state = await _trackingService.GetUserStateAsync(username, cancellationToken);

            return Ok(new InboxSnapshotResponse(contexts, enrichedReports, 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 StatusCode(ex.StatusCode, 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 cargar la bandeja: {ex.GetType().Name}: {ex.Message}"));
        }
    }

    [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);

            var zip = await _globalLeaksClient.DownloadReportZipAsync(session.SessionId!, report.Id, 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(zip, json, 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 StatusCode(ex.StatusCode, new ApiError(ex.Message));
        }
        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: {ex.GetType().Name}: {ex.Message}"));
        }
    }

    [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 StatusCode(ex.StatusCode, new ApiError(ex.Message));
        }
        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 leer el detalle de la denuncia: {ex.GetType().Name}: {ex.Message}"));
        }
    }

    [HttpPost("local/ensure-storage")]
    public async Task<IActionResult> EnsureStorage(CancellationToken cancellationToken)
    {
        await _inboxService.EnsureStorageReadyAsync(cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpPost("local/process")]
    public async Task<ActionResult<ImportSummary>> ProcessLocalZips(CancellationToken cancellationToken)
        => Ok(await _inboxService.ProcessPendingFolderZipsAsync(cancellationToken));

    [HttpGet("local/zips")]
    public async Task<ActionResult<IReadOnlyList<string>>> GetLocalZips(CancellationToken cancellationToken)
        => Ok(await _inboxService.GetExistingZipNamesAsync(cancellationToken));

    [HttpDelete("local/zips/{zipName}")]
    public async Task<IActionResult> DeleteLocalZip(string zipName, CancellationToken cancellationToken)
    {
        await _inboxService.DeleteZipAsync(zipName, cancellationToken);
        return Ok(new { ok = true });
    }

    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);
}
