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

namespace ApiDenuncias.Controllers;

[ApiController]
[Authorize]
[Route("api/denuncias")]
public sealed class DenunciasController : ControllerBase
{
    private readonly IDenunciaStore _denunciaStore;
    private readonly IFilteredDenunciaStore _filteredDenunciaStore;
    private readonly UserComplaintAccessService _accessService;

    public DenunciasController(
        IDenunciaStore denunciaStore,
        IFilteredDenunciaStore filteredDenunciaStore,
        UserComplaintAccessService accessService)
    {
        _denunciaStore = denunciaStore;
        _filteredDenunciaStore = filteredDenunciaStore;
        _accessService = accessService;
    }

    [HttpPost("schema/ensure")]
    public async Task<IActionResult> EnsureSchema(CancellationToken cancellationToken)
    {
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpGet]
    public async Task<ActionResult<List<DenunciasGestiona>>> GetAll(
        [FromQuery] DenunciaListScope scope,
        CancellationToken cancellationToken)
    {
        var allowedIds = await GetAllowedIdsAsync(cancellationToken);
        if (allowedIds.Count == 0)
        {
            return Ok(new List<DenunciasGestiona>());
        }

        return Ok(await _filteredDenunciaStore.GetDenunciasByIdsAsync(allowedIds, scope, cancellationToken));
    }

    [HttpGet("{denunciaId:int}")]
    public async Task<ActionResult<DenunciasGestiona?>> GetById(int denunciaId, CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return NotFound();
        }

        return Ok(await _denunciaStore.GetDenunciaByIdAsync(denunciaId, cancellationToken));
    }

    [HttpPost]
    public async Task<IActionResult> Upsert(DenunciasGestiona denuncia, CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denuncia.Id_Denuncia, cancellationToken))
        {
            return Forbid();
        }

        await _denunciaStore.UpsertDenunciaAsync(denuncia, cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpGet("ficheros")]
    public async Task<ActionResult<List<FicherosDenuncias>>> GetAllFicheros(CancellationToken cancellationToken)
    {
        var allowedIds = await GetAllowedIdsAsync(cancellationToken);
        if (allowedIds.Count == 0)
        {
            return Ok(new List<FicherosDenuncias>());
        }

        return Ok(await _filteredDenunciaStore.GetFicherosByDenunciaIdsAsync(allowedIds, cancellationToken));
    }

    [HttpGet("{denunciaId:int}/ficheros")]
    public async Task<ActionResult<List<FicherosDenuncias>>> GetFicherosByDenuncia(int denunciaId, CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return NotFound();
        }

        return Ok(await _denunciaStore.GetFicherosByDenunciaAsync(denunciaId, cancellationToken));
    }

    [HttpGet("{denunciaId:int}/ficheros/content")]
    public async Task<IActionResult> GetFicheroContent(
        int denunciaId,
        [FromQuery] string fileName,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return BadRequest(new ApiError("Nombre de fichero obligatorio."));
        }

        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return NotFound();
        }

        var ficheros = await _denunciaStore.GetFicherosByDenunciaAsync(denunciaId, cancellationToken);
        var fichero = ficheros.FirstOrDefault(file =>
            string.Equals(file.NombreFichero, fileName, StringComparison.Ordinal));

        if (fichero?.Fichero is not { Length: > 0 } bytes)
        {
            return NotFound();
        }

        return File(
            bytes,
            GetAttachmentContentType(fichero.NombreFichero),
            enableRangeProcessing: true);
    }

    [HttpPost("ficheros")]
    public async Task<IActionResult> UpsertFicheros(UpsertFicherosRequest request, CancellationToken cancellationToken)
    {
        var ficheros = request.Ficheros ?? [];
        foreach (var denunciaId in ficheros.Select(f => f.Id_Denuncia).Distinct())
        {
            if (!await CanAccessAsync(denunciaId, cancellationToken))
            {
                return Forbid();
            }
        }

        await _denunciaStore.UpsertFicherosAsync(ficheros, cancellationToken);
        return Ok(new { ok = true });
    }

    [HttpPost("{denunciaId:int}/ficheros/mark-uploaded")]
    public async Task<IActionResult> MarkFicherosAsUploaded(
        int denunciaId,
        MarkFicherosUploadedRequest request,
        CancellationToken cancellationToken)
    {
        if (!await CanAccessAsync(denunciaId, cancellationToken))
        {
            return Forbid();
        }

        await _denunciaStore.MarkFicherosAsUploadedAsync(
            denunciaId,
            request.FileNames,
            request.UploadedAtUtc,
            cancellationToken);

        return Ok(new { ok = true });
    }

    private async Task<HashSet<int>> GetAllowedIdsAsync(CancellationToken cancellationToken)
    {
        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return await _accessService.GetAllowedComplaintIdsAsync(GetUsername(), cancellationToken);
    }

    private async Task<bool> CanAccessAsync(int denunciaId, CancellationToken cancellationToken)
    {
        if (denunciaId <= 0)
        {
            return false;
        }

        await _denunciaStore.EnsureSchemaAsync(cancellationToken);
        return await _accessService.CanAccessComplaintAsync(GetUsername(), denunciaId, cancellationToken);
    }

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

    private static string GetAttachmentContentType(string? fileName)
    {
        return Path.GetExtension(fileName ?? string.Empty).ToLowerInvariant() switch
        {
            ".pdf" => "application/pdf",
            ".txt" => "text/plain",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".png" => "image/png",
            ".gif" => "image/gif",
            ".zip" => "application/zip",
            _ => "application/octet-stream"
        };
    }
}
