using FluentValidation;
using MediatR;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations.Dtos;
using SolutionErp.Domain.Contracts;   // ChangelogAction
using SolutionErp.Domain.PurchaseEvaluations;

namespace SolutionErp.Application.PurchaseEvaluations;

// Mirror ContractAttachmentFeatures. Key khác: `PurchaseEvaluationSupplierId`
// nullable → upload file gắn với 1 NCC cụ thể (để đối chiếu báo giá) hoặc
// tổng quan phiếu. Reuse IFileStorage + LocalFileStorage.

// ========== UPLOAD ==========

public record UploadPurchaseEvaluationAttachmentCommand(
    Guid PurchaseEvaluationId,
    Guid? PurchaseEvaluationSupplierId,
    string FileName,
    string ContentType,
    long FileSize,
    Stream Content,
    PurchaseEvaluationAttachmentPurpose Purpose,
    string? Note) : IRequest<PurchaseEvaluationAttachmentDto>;

public class UploadPurchaseEvaluationAttachmentCommandValidator
    : AbstractValidator<UploadPurchaseEvaluationAttachmentCommand>
{
    private const long MaxBytes = 20L * 1024 * 1024;

    private static readonly HashSet<string> AllowedContentTypes = new(StringComparer.OrdinalIgnoreCase)
    {
        "application/pdf",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/msword",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.ms-excel",
        "image/png",
        "image/jpeg",
        "image/webp",
    };

    public UploadPurchaseEvaluationAttachmentCommandValidator()
    {
        RuleFor(x => x.PurchaseEvaluationId).NotEmpty();
        RuleFor(x => x.FileName).NotEmpty().MaximumLength(255);
        RuleFor(x => x.FileSize).GreaterThan(0).LessThanOrEqualTo(MaxBytes)
            .WithMessage("File vượt quá 20 MB.");
        RuleFor(x => x.ContentType).Must(c => AllowedContentTypes.Contains(c))
            .WithMessage("Định dạng file không được hỗ trợ. Cho phép: pdf, doc(x), xls(x), png, jpg, webp.");
        RuleFor(x => x.Purpose).IsInEnum();
        RuleFor(x => x.Note).MaximumLength(500);
    }
}

public class UploadPurchaseEvaluationAttachmentCommandHandler(
    IApplicationDbContext db,
    IFileStorage storage,
    ICurrentUser currentUser) : IRequestHandler<UploadPurchaseEvaluationAttachmentCommand, PurchaseEvaluationAttachmentDto>
{
    public async Task<PurchaseEvaluationAttachmentDto> Handle(
        UploadPurchaseEvaluationAttachmentCommand request, CancellationToken ct)
    {
        var ev = await db.PurchaseEvaluations.FirstOrDefaultAsync(e => e.Id == request.PurchaseEvaluationId, ct)
            ?? throw new NotFoundException("PurchaseEvaluation", request.PurchaseEvaluationId);

        // Verify supplier-row thuộc cùng phiếu (nếu gắn)
        if (request.PurchaseEvaluationSupplierId is Guid sid)
        {
            var supOk = await db.PurchaseEvaluationSuppliers
                .AnyAsync(s => s.Id == sid && s.PurchaseEvaluationId == ev.Id, ct);
            if (!supOk) throw new NotFoundException("PurchaseEvaluationSupplier", sid);
        }

        var attId = Guid.NewGuid();
        var safeName = SanitizeFileName(request.FileName);
        var relativePath = $"purchase-evaluations/{ev.Id}/{attId}_{safeName}";

        await storage.SaveAsync(relativePath, request.Content, ct);

        var entity = new PurchaseEvaluationAttachment
        {
            Id = attId,
            PurchaseEvaluationId = ev.Id,
            PurchaseEvaluationSupplierId = request.PurchaseEvaluationSupplierId,
            FileName = request.FileName,
            StoragePath = relativePath,
            FileSize = request.FileSize,
            ContentType = request.ContentType,
            Purpose = request.Purpose,
            Note = request.Note,
        };
        db.PurchaseEvaluationAttachments.Add(entity);

        db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
        {
            PurchaseEvaluationId = ev.Id,
            EntityType = PurchaseEvaluationEntityType.Attachment,
            EntityId = attId,
            Action = ChangelogAction.Insert,
            PhaseAtChange = ev.Phase,
            UserId = currentUser.UserId,
            Summary = $"Tải lên file: {entity.FileName}",
        });

        await db.SaveChangesAsync(ct);

        return new PurchaseEvaluationAttachmentDto(
            entity.Id, entity.PurchaseEvaluationSupplierId, entity.FileName, entity.StoragePath,
            entity.FileSize, entity.ContentType, entity.Purpose, entity.Note, DateTime.UtcNow);
    }

    private static string SanitizeFileName(string name)
    {
        var baseName = Path.GetFileName(name);
        foreach (var c in Path.GetInvalidFileNameChars())
            baseName = baseName.Replace(c, '_');
        baseName = baseName.TrimStart('.');
        if (string.IsNullOrWhiteSpace(baseName)) baseName = "file";
        return baseName.Length > 200 ? baseName[^200..] : baseName;
    }
}

// ========== DOWNLOAD ==========

public record DownloadPurchaseEvaluationAttachmentQuery(Guid PurchaseEvaluationId, Guid AttachmentId)
    : IRequest<PurchaseEvaluationAttachmentFile>;

public record PurchaseEvaluationAttachmentFile(Stream Content, string FileName, string ContentType);

public class DownloadPurchaseEvaluationAttachmentQueryHandler(
    IApplicationDbContext db,
    IFileStorage storage) : IRequestHandler<DownloadPurchaseEvaluationAttachmentQuery, PurchaseEvaluationAttachmentFile>
{
    public async Task<PurchaseEvaluationAttachmentFile> Handle(
        DownloadPurchaseEvaluationAttachmentQuery request, CancellationToken ct)
    {
        var att = await db.PurchaseEvaluationAttachments.AsNoTracking()
            .FirstOrDefaultAsync(a => a.Id == request.AttachmentId && a.PurchaseEvaluationId == request.PurchaseEvaluationId, ct)
            ?? throw new NotFoundException("Attachment", request.AttachmentId);

        var stream = await storage.OpenReadAsync(att.StoragePath, ct);
        return new PurchaseEvaluationAttachmentFile(stream, att.FileName, att.ContentType);
    }
}

// ========== DELETE ==========

public record DeletePurchaseEvaluationAttachmentCommand(Guid PurchaseEvaluationId, Guid AttachmentId) : IRequest;

public class DeletePurchaseEvaluationAttachmentCommandHandler(
    IApplicationDbContext db,
    IFileStorage storage,
    ICurrentUser currentUser) : IRequestHandler<DeletePurchaseEvaluationAttachmentCommand>
{
    public async Task Handle(DeletePurchaseEvaluationAttachmentCommand request, CancellationToken ct)
    {
        var att = await db.PurchaseEvaluationAttachments
            .FirstOrDefaultAsync(a => a.Id == request.AttachmentId && a.PurchaseEvaluationId == request.PurchaseEvaluationId, ct)
            ?? throw new NotFoundException("Attachment", request.AttachmentId);
        var ev = await db.PurchaseEvaluations.AsNoTracking()
            .FirstOrDefaultAsync(e => e.Id == request.PurchaseEvaluationId, ct);

        db.PurchaseEvaluationAttachments.Remove(att);

        db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
        {
            PurchaseEvaluationId = request.PurchaseEvaluationId,
            EntityType = PurchaseEvaluationEntityType.Attachment,
            EntityId = att.Id,
            Action = ChangelogAction.Delete,
            PhaseAtChange = ev?.Phase ?? PurchaseEvaluationPhase.DangSoanThao,
            UserId = currentUser.UserId,
            Summary = $"Xóa file: {att.FileName}",
        });

        await db.SaveChangesAsync(ct);

        try { await storage.DeleteAsync(att.StoragePath, ct); }
        catch { /* best-effort */ }
    }
}
