using System.Security.Claims;
using Books.Api.Authorization;
using Books.Api.Commands.Attachments;
using Books.Api.Domain;
using Books.Api.Domain.Attachments;
using Books.Api.EventFlow.ReadModels;
using Books.Api.EventFlow.Repositories;
using Books.Api.Infrastructure.FileStorage;
using EventFlow;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Books.Api.Controllers;

/// <summary>
/// REST API for attachment (bilag) file operations.
/// GraphQL doesn't handle file uploads well, so we use REST for this.
/// </summary>
[ApiController]
[Route("api/attachments")]
[Authorize]
public class AttachmentController(
    ICommandBus commandBus,
    IAttachmentRepository attachmentRepository,
    IFileStorageService fileStorage,
    ICompanyAccessService companyAccess,
    ILogger<AttachmentController> logger) : ControllerBase
{
    /// <summary>
    /// Upload one or more attachments for a company.
    /// </summary>
    /// <param name="companyId">Company ID</param>
    /// <param name="draftId">Optional draft ID to link attachments to</param>
    /// <param name="files">Files to upload</param>
    [HttpPost("upload")]
    [RequestSizeLimit(50 * 1024 * 1024)] // 50MB max total
    public async Task<IActionResult> Upload(
        [FromQuery] string companyId,
        [FromQuery] string? draftId,
        [FromForm] List<IFormFile> files,
        CancellationToken cancellationToken)
    {
        // Validate company access
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(userId))
        {
            return Unauthorized(new { error = "NOT_AUTHENTICATED", message = "You must be authenticated to upload attachments" });
        }

        // Check if user can write (Accountant or Owner role)
        var canWrite = await companyAccess.CanWriteAsync(companyId, cancellationToken);
        if (!canWrite)
        {
            return Forbid();
        }

        if (files.Count == 0)
        {
            return BadRequest(new { error = "NO_FILES", message = "No files provided" });
        }

        var results = new List<object>();

        foreach (var file in files)
        {
            try
            {
                // Validate file size (10MB per file)
                if (file.Length > 10 * 1024 * 1024)
                {
                    results.Add(new
                    {
                        success = false,
                        fileName = file.FileName,
                        error = "FILE_TOO_LARGE",
                        message = $"File '{file.FileName}' exceeds 10MB limit"
                    });
                    continue;
                }

                // Store file
                await using var stream = file.OpenReadStream();
                var storageResult = await fileStorage.StoreAsync(
                    companyId,
                    file.FileName,
                    file.ContentType,
                    stream,
                    cancellationToken);

                // Create attachment aggregate
                var attachmentId = AttachmentId.New;
                var command = new UploadAttachmentCommand(
                    attachmentId,
                    companyId,
                    storageResult.StoredFileName,
                    file.FileName,
                    file.ContentType,
                    storageResult.FileSize,
                    storageResult.StoragePath,
                    userId,
                    draftId);

                await commandBus.PublishAsync(command, cancellationToken);

                // Wait briefly for read model to be updated (eventual consistency)
                await Task.Delay(100, cancellationToken);

                var attachment = await attachmentRepository.GetByIdAsync(attachmentId.Value, cancellationToken);

                results.Add(new
                {
                    success = true,
                    id = attachmentId.Value,
                    fileName = file.FileName,
                    fileType = file.ContentType,
                    fileSize = storageResult.FileSize,
                    uploadedAt = attachment?.UploadedAt ?? DateTimeOffset.UtcNow,
                    url = fileStorage.GetDownloadUrl(storageResult.StoragePath)
                });
            }
            catch (DomainException ex)
            {
                logger.LogWarning(ex, "Failed to upload file {FileName}", file.FileName);
                results.Add(new
                {
                    success = false,
                    fileName = file.FileName,
                    error = ex.Code,
                    message = ex.MessageDanish
                });
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Unexpected error uploading file {FileName}", file.FileName);
                results.Add(new
                {
                    success = false,
                    fileName = file.FileName,
                    error = "UPLOAD_FAILED",
                    message = "An unexpected error occurred"
                });
            }
        }

        return Ok(new { attachments = results });
    }

    /// <summary>
    /// Get attachments for a draft.
    /// </summary>
    [HttpGet("draft/{draftId}")]
    public async Task<IActionResult> GetByDraft(string draftId, CancellationToken cancellationToken)
    {
        var attachments = await attachmentRepository.GetByDraftIdAsync(draftId, cancellationToken);

        if (attachments.Count == 0)
        {
            return Ok(new { attachments = Array.Empty<object>() });
        }

        // Validate company access (use first attachment's company)
        var access = await companyAccess.GetAccessAsync(attachments[0].CompanyId, cancellationToken);
        if (access == null)
        {
            return Forbid();
        }

        var result = attachments.Select(a => new
        {
            id = a.Id,
            fileName = a.OriginalFileName,
            fileType = a.ContentType,
            fileSize = a.FileSize,
            uploadedAt = a.UploadedAt.ToString("O"),
            uploadedBy = a.UploadedBy,
            url = fileStorage.GetDownloadUrl(a.StoragePath)
        });

        return Ok(new { attachments = result });
    }

    /// <summary>
    /// Download an attachment by storage path.
    /// </summary>
    [HttpGet("{*storagePath}")]
    public async Task<IActionResult> Download(string storagePath, CancellationToken cancellationToken)
    {
        // Validate path to prevent directory traversal attacks
        if (string.IsNullOrWhiteSpace(storagePath) ||
            storagePath.Contains("..") ||
            storagePath.Contains("~") ||
            Path.IsPathRooted(storagePath) ||
            storagePath.StartsWith("/") ||
            storagePath.StartsWith("\\"))
        {
            logger.LogWarning("Attempted path traversal attack with path: {StoragePath}", storagePath);
            return BadRequest(new { error = "INVALID_PATH", message = "Invalid storage path" });
        }

        var file = await fileStorage.GetAsync(storagePath, cancellationToken);

        if (file == null)
        {
            return NotFound(new { error = "FILE_NOT_FOUND", message = "Attachment not found" });
        }

        return File(file.Content, file.ContentType, file.FileName);
    }

    /// <summary>
    /// Delete an attachment.
    /// Note: Per Bogføringsloven § 6, attachments linked to transactions
    /// cannot be deleted within the 5-year retention period.
    /// </summary>
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(
        string id,
        [FromQuery] string reason,
        CancellationToken cancellationToken)
    {
        var attachment = await attachmentRepository.GetByIdAsync(id, cancellationToken);

        if (attachment == null)
        {
            return NotFound(new { error = "ATTACHMENT_NOT_FOUND", message = "Attachment not found" });
        }

        // Validate company access - need write permission to delete
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(userId))
        {
            return Unauthorized();
        }

        var canWrite = await companyAccess.CanWriteAsync(attachment.CompanyId, cancellationToken);
        if (!canWrite)
        {
            return Forbid();
        }

        if (string.IsNullOrWhiteSpace(reason))
        {
            return BadRequest(new
            {
                error = "REASON_REQUIRED",
                message = "A reason for deletion is required (Bogføringsloven § 6)"
            });
        }

        try
        {
            var command = new DeleteAttachmentCommand(
                new AttachmentId(id),
                userId,
                reason);

            await commandBus.PublishAsync(command, cancellationToken);

            // Also delete the physical file
            await fileStorage.DeleteAsync(attachment.StoragePath, cancellationToken);

            return Ok(new { success = true, message = "Attachment deleted" });
        }
        catch (DomainException ex)
        {
            return BadRequest(new { error = ex.Code, message = ex.MessageDanish });
        }
    }
}
