using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OmsorgCore.Api.Contracts;
using OmsorgCore.Api.Security;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;
using OmsorgCore.Engine.Events;

namespace OmsorgCore.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/documents")]
public class DocumentsController : ControllerBase
{
    private readonly IDocumentService _documentService;
    private readonly IEmployeeRepository _employeeRepository;
    private readonly ICurrentUserService _currentUserService;
    private readonly IDomainEventDispatcher _dispatcher;

    public DocumentsController(
        IDocumentService documentService,
        IEmployeeRepository employeeRepository,
        ICurrentUserService currentUserService,
        IDomainEventDispatcher dispatcher)
    {
        _documentService = documentService;
        _employeeRepository = employeeRepository;
        _currentUserService = currentUserService;
        _dispatcher = dispatcher;
    }

    [HttpGet]
    [RequirePermission(ModuleType.Documents, PermissionAction.View)]
    public async Task<ActionResult<IReadOnlyList<DocumentResponse>>> GetByEntity(
        [FromQuery] string entityType,
        [FromQuery] Guid entityId,
        CancellationToken cancellationToken)
    {
        if (!Enum.TryParse<DocumentEntityType>(entityType, out _))
        {
            return BadRequest($"entityType muss einer der folgenden Werte sein: {string.Join(", ", Enum.GetNames<DocumentEntityType>())}.");
        }

        var documents = await _documentService.GetByEntityAsync(entityType, entityId, cancellationToken);
        return Ok(documents.Select(ToResponse).ToList());
    }

    [HttpPost]
    [RequirePermission(ModuleType.Documents, PermissionAction.Create)]
    [Consumes("multipart/form-data")]
    public async Task<ActionResult<DocumentResponse>> Upload([FromForm] UploadDocumentRequest request, CancellationToken cancellationToken)
    {
        if (!Enum.TryParse<DocumentEntityType>(request.EntityType, out var entityType))
        {
            return BadRequest($"EntityType muss einer der folgenden Werte sein: {string.Join(", ", Enum.GetNames<DocumentEntityType>())}.");
        }

        if (entityType == DocumentEntityType.Employee)
        {
            var employee = await _employeeRepository.GetByIdAsync(request.EntityId, cancellationToken);
            if (employee is null)
            {
                return BadRequest("EntityId verweist auf keinen existierenden Mitarbeiter.");
            }
        }
        else
        {
            return BadRequest("Dokumente sind aktuell nur für EntityType=Employee möglich.");
        }

        if (request.File is null || request.File.Length == 0)
        {
            return BadRequest("File ist erforderlich.");
        }

        await using var stream = request.File.OpenReadStream();
        var result = await _documentService.UploadAsync(
            request.EntityType,
            request.EntityId,
            request.Category,
            request.Description,
            request.File.FileName,
            request.File.ContentType,
            request.File.Length,
            stream,
            _currentUserService.UserId!.Value,
            cancellationToken);

        if (result.Error != DocumentUploadError.None || result.Document is null)
        {
            return BadRequest(ToErrorMessage(result.Error));
        }

        var created = await _documentService.GetByIdAsync(result.Document.Id, cancellationToken) ?? result.Document;
        return CreatedAtAction(nameof(GetByEntity), new { entityType = created.EntityType, entityId = created.EntityId }, ToResponse(created));
    }

    [HttpPut("{id:guid}")]
    [RequirePermission(ModuleType.Documents, PermissionAction.Edit)]
    public async Task<ActionResult<DocumentResponse>> Update(Guid id, UpdateDocumentRequest request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.FileName) || request.FileName.Length > 260)
        {
            return BadRequest("FileName ist erforderlich und darf maximal 260 Zeichen lang sein.");
        }

        var result = await _documentService.UpdateAsync(id, request.Category, request.Description, request.FileName, cancellationToken);

        return result.Error switch
        {
            DocumentUpdateError.NotFound => NotFound(),
            DocumentUpdateError.InvalidCategory => BadRequest("Category ist ungültig."),
            _ => Ok(ToResponse(result.Document!))
        };
    }

    [HttpGet("{id:guid}/download")]
    [RequirePermission(ModuleType.Documents, PermissionAction.View)]
    public async Task<IActionResult> Download(Guid id, CancellationToken cancellationToken)
    {
        var document = await _documentService.GetByIdAsync(id, cancellationToken);
        if (document is null)
        {
            return NotFound();
        }

        var stream = await _documentService.OpenForDownloadAsync(id, cancellationToken);
        if (stream is null)
        {
            return NotFound();
        }

        await _dispatcher.DispatchAsync(
            new AuditEvent(
                _currentUserService.UserId,
                _currentUserService.Username,
                _currentUserService.IpAddress,
                "DocumentDownloaded",
                $"{{\"documentId\":\"{document.Id}\",\"fileName\":\"{document.FileName}\"}}"),
            cancellationToken);

        return File(stream, document.ContentType, document.FileName);
    }

    [HttpDelete("{id:guid}")]
    [RequirePermission(ModuleType.Documents, PermissionAction.Delete)]
    public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
    {
        var deleted = await _documentService.DeleteAsync(id, cancellationToken);
        return deleted ? NoContent() : NotFound();
    }

    private static string ToErrorMessage(DocumentUploadError error) => error switch
    {
        DocumentUploadError.InvalidCategory => "Category ist ungültig.",
        DocumentUploadError.FileTooLarge => "Die Datei überschreitet die maximal erlaubte Größe.",
        DocumentUploadError.ContentTypeNotAllowed => "Dieser Dateityp ist nicht erlaubt.",
        _ => "Upload fehlgeschlagen."
    };

    private static DocumentResponse ToResponse(Document document)
        => new(
            document.Id,
            document.EntityType,
            document.EntityId,
            document.Category,
            document.FileName,
            document.ContentType,
            document.SizeBytes,
            document.Description,
            document.UploadedByUserId,
            document.UploadedByUser?.Username,
            document.CreatedAt);
}
