using FluentValidation;
using MediatR;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.PurchaseEvaluations;

namespace SolutionErp.Application.PurchaseEvaluations;

// [S61 Mig 50 · S76 PRO split] 2 handler nhập ngân sách gói thầu MA TRẬN theo ROLE:
//   - PRO (Procurement | Admin): ProInitialAmount ("Ban hành lần đầu") +
//     ProAdjustmentAmount ("V0/hiệu chỉnh" — cho phép ÂM) + ProNote. [S76 tách 2 số;
//     ProEstimateAmount cũ migrate → ProInitialAmount qua Mig 56]
//   - CCM (CostControl | Admin): InitialAmount ("Ban hành lần đầu") +
//     AdjustmentAmount ("V0/hiệu chỉnh tăng giảm" — cho phép ÂM) + CcmNote (Mig 55).
// Authz pattern AssignItTicketHandler S54: controller [Authorize] any-auth,
// handler fine-grained ForbiddenException fail-closed (Forbidden TRƯỚC mọi
// side-effect — S56 #5). KHÔNG ràng Phase (CCM "nhập trong khi duyệt" theo lời
// anh = thời điểm nghiệp vụ, không hard-block — bảng ngân sách là tài liệu sống
// per cặp, chỉnh được bất kỳ lúc nào như file Excel; trade-off ghi nhận).
// Record PeWorkItemBudgets resolve qua PE (FE đang mở phiếu) — phiếu cũ chưa gắn
// Hạng mục → Conflict. Record chưa có → auto-create (cùng helper với Create PE).

// ===== Helper dùng chung (Create PE dùng bản pre-check riêng; PUT pro/ccm dùng đây) =====

internal static class PeWorkItemBudgetEnsurer
{
    /// <summary>
    /// Load record TRACKED của cặp (ProjectId, WorkItemId); chưa có → tạo mới.
    /// Race-safe theo advise database-agent S61: UNIQUE filtered index = arbiter
    /// cuối; thua race → DbUpdateException → detach + re-fetch record bên thắng;
    /// lỗi KHÁC (không phải thua-race) → rethrow, KHÔNG nuốt.
    /// </summary>
    public static async Task<PeWorkItemBudget> EnsureTrackedAsync(
        IApplicationDbContext db, Guid projectId, Guid workItemId, CancellationToken ct)
    {
        var existing = await db.PeWorkItemBudgets
            .FirstOrDefaultAsync(b => b.ProjectId == projectId && b.WorkItemId == workItemId, ct);
        if (existing is not null) return existing;

        var rec = new PeWorkItemBudget { ProjectId = projectId, WorkItemId = workItemId };
        db.PeWorkItemBudgets.Add(rec);
        try
        {
            await db.SaveChangesAsync(ct);
            return rec;
        }
        catch (DbUpdateException)
        {
            ((DbContext)db).Entry(rec).State = EntityState.Detached;
            var winner = await db.PeWorkItemBudgets
                .FirstOrDefaultAsync(b => b.ProjectId == projectId && b.WorkItemId == workItemId, ct);
            if (winner is null) throw; // không phải thua-race → lỗi thật phải nổi
            return winner;
        }
    }
}

// ===== PRO — dự trù lần đầu + ghi chú =====

public record UpdatePeBudgetProCommand(
    Guid PeId,
    decimal? ProInitialAmount,
    decimal? ProAdjustmentAmount,
    string? ProNote) : IRequest;

public class UpdatePeBudgetProCommandValidator : AbstractValidator<UpdatePeBudgetProCommand>
{
    public UpdatePeBudgetProCommandValidator()
    {
        RuleFor(x => x.ProInitialAmount).GreaterThanOrEqualTo(0)
            .When(x => x.ProInitialAmount.HasValue);
        // ProAdjustmentAmount KHÔNG ràng dấu — "hiệu chỉnh tăng giảm" cho phép ÂM.
        RuleFor(x => x.ProNote).MaximumLength(1000);
    }
}

public class UpdatePeBudgetProCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser) : IRequestHandler<UpdatePeBudgetProCommand>
{
    public async Task Handle(UpdatePeBudgetProCommand request, CancellationToken ct)
    {
        var pe = await db.PurchaseEvaluations.FirstOrDefaultAsync(x => x.Id == request.PeId, ct)
            ?? throw new NotFoundException("PurchaseEvaluation", request.PeId);

        // Fail-closed TRƯỚC mọi side-effect (kể cả auto-create record).
        if (!currentUser.Roles.Contains(AppRoles.Admin)
            && !currentUser.Roles.Contains(AppRoles.Procurement))
        {
            throw new ForbiddenException(
                "Chỉ Phòng Cung ứng (PRO) hoặc Admin được nhập dự trù ngân sách gói thầu.");
        }
        if (pe.WorkItemId is not Guid workItemId)
            throw new ConflictException(
                "Phiếu chưa gắn Hạng mục công việc — gắn Hạng mục trước khi nhập ngân sách gói thầu.");

        var rec = await PeWorkItemBudgetEnsurer.EnsureTrackedAsync(db, pe.ProjectId, workItemId, ct);

        var oldInitial = rec.ProInitialAmount;
        var oldAdjustment = rec.ProAdjustmentAmount;
        var oldNote = rec.ProNote;
        rec.ProInitialAmount = request.ProInitialAmount;     // absolute-set (null = clear)
        rec.ProAdjustmentAmount = request.ProAdjustmentAmount;
        rec.ProNote = request.ProNote;

        var parts = new List<string>();
        if (oldInitial != request.ProInitialAmount)
            parts.Add($"ban hành PRO {oldInitial?.ToString("N0") ?? "(trống)"}đ → {request.ProInitialAmount?.ToString("N0") ?? "(trống)"}đ");
        if (oldAdjustment != request.ProAdjustmentAmount)
            parts.Add($"V0/hiệu chỉnh PRO {oldAdjustment?.ToString("N0") ?? "(trống)"}đ → {request.ProAdjustmentAmount?.ToString("N0") ?? "(trống)"}đ");
        if (oldNote != request.ProNote)
            parts.Add("ghi chú PRO cập nhật");

        db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
        {
            PurchaseEvaluationId = pe.Id,
            EntityType = PurchaseEvaluationEntityType.Header,
            Action = ChangelogAction.Update,
            PhaseAtChange = pe.Phase,
            UserId = currentUser.UserId,
            UserName = currentUser.FullName ?? currentUser.Email,
            Summary = $"Ngân sách gói thầu (PRO): {(parts.Count == 0 ? "không đổi" : string.Join(", ", parts))}",
        });

        await db.SaveChangesAsync(ct);
    }
}

// ===== CCM — Ban hành lần đầu + V0/hiệu chỉnh (nhập thực tế trong khi duyệt) =====

public record UpdatePeBudgetCcmCommand(
    Guid PeId,
    decimal? InitialAmount,
    decimal? AdjustmentAmount,
    string? CcmNote) : IRequest;

public class UpdatePeBudgetCcmCommandValidator : AbstractValidator<UpdatePeBudgetCcmCommand>
{
    public UpdatePeBudgetCcmCommandValidator()
    {
        RuleFor(x => x.InitialAmount).GreaterThanOrEqualTo(0)
            .When(x => x.InitialAmount.HasValue);
        // AdjustmentAmount KHÔNG ràng dấu — "hiệu chỉnh tăng giảm" cho phép ÂM.
        RuleFor(x => x.CcmNote).MaximumLength(1000);
    }
}

public class UpdatePeBudgetCcmCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser) : IRequestHandler<UpdatePeBudgetCcmCommand>
{
    public async Task Handle(UpdatePeBudgetCcmCommand request, CancellationToken ct)
    {
        var pe = await db.PurchaseEvaluations.FirstOrDefaultAsync(x => x.Id == request.PeId, ct)
            ?? throw new NotFoundException("PurchaseEvaluation", request.PeId);

        if (!currentUser.Roles.Contains(AppRoles.Admin)
            && !currentUser.Roles.Contains(AppRoles.CostControl))
        {
            throw new ForbiddenException(
                "Chỉ Phòng Kiểm soát Chi phí (CCM) hoặc Admin được nhập ngân sách ban hành/hiệu chỉnh.");
        }
        if (pe.WorkItemId is not Guid workItemId)
            throw new ConflictException(
                "Phiếu chưa gắn Hạng mục công việc — gắn Hạng mục trước khi nhập ngân sách gói thầu.");

        var rec = await PeWorkItemBudgetEnsurer.EnsureTrackedAsync(db, pe.ProjectId, workItemId, ct);

        var oldInitial = rec.InitialAmount;
        var oldAdjustment = rec.AdjustmentAmount;
        var oldCcmNote = rec.CcmNote;
        rec.InitialAmount = request.InitialAmount;           // absolute-set (null = clear)
        rec.AdjustmentAmount = request.AdjustmentAmount;
        rec.CcmNote = request.CcmNote;

        var parts = new List<string>();
        if (oldInitial != request.InitialAmount)
            parts.Add($"ban hành lần đầu {oldInitial?.ToString("N0") ?? "(trống)"}đ → {request.InitialAmount?.ToString("N0") ?? "(trống)"}đ");
        if (oldAdjustment != request.AdjustmentAmount)
            parts.Add($"V0/hiệu chỉnh {oldAdjustment?.ToString("N0") ?? "(trống)"}đ → {request.AdjustmentAmount?.ToString("N0") ?? "(trống)"}đ");
        if (oldCcmNote != request.CcmNote)
            parts.Add("ghi chú CCM cập nhật");

        db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
        {
            PurchaseEvaluationId = pe.Id,
            EntityType = PurchaseEvaluationEntityType.Header,
            Action = ChangelogAction.Update,
            PhaseAtChange = pe.Phase,
            UserId = currentUser.UserId,
            UserName = currentUser.FullName ?? currentUser.Email,
            Summary = $"Ngân sách gói thầu (CCM): {(parts.Count == 0 ? "không đổi" : string.Join(", ", parts))}",
        });

        await db.SaveChangesAsync(ct);
    }
}

// ===== CCM — "NS kỳ này" cột CCM (Section B THỰC HIỆN tách 3 cột Dự án|PRO|CCM) =====
// [S88] Chuyển từ PeSuggestedPriceFeatures.cs về đây (đúng nhóm budget setter).
// [Mig 59 2026-06-24 — anh Kiệt FDC / CCM] PRO dùng BudgetPeriodAmount sẵn có (luồng
// /budget-adjust). CCM nhập "NS kỳ này" RIÊNG ở cột mình → tính thực-hiện độc-lập.
// Authz mirror UpdatePeSuggestedPriceCcm: controller [Authorize] any-auth, handler
// ForbiddenException fail-closed TRƯỚC mọi side-effect (S56 #5). Budget=0 hợp lệ
// (GreaterThanOrEqualTo(0).When(HasValue)). Absolute-set (null=clear). KHÔNG
// submit-guard (guard chỉ trên BudgetPeriodAmount của PRO, KHÔNG đụng — gotcha #73).

public record SetPeCcmBudgetPeriodCommand(
    Guid Id,
    decimal? CcmBudgetPeriodAmount) : IRequest;

public class SetPeCcmBudgetPeriodCommandValidator : AbstractValidator<SetPeCcmBudgetPeriodCommand>
{
    public SetPeCcmBudgetPeriodCommandValidator()
    {
        RuleFor(x => x.CcmBudgetPeriodAmount).GreaterThanOrEqualTo(0)   // 0 hợp lệ
            .When(x => x.CcmBudgetPeriodAmount.HasValue);
    }
}

public class SetPeCcmBudgetPeriodCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser) : IRequestHandler<SetPeCcmBudgetPeriodCommand>
{
    public async Task Handle(SetPeCcmBudgetPeriodCommand request, CancellationToken ct)
    {
        var pe = await db.PurchaseEvaluations.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("PurchaseEvaluation", request.Id);

        // [S133] Phiếu KẾT THÚC (DaDuyet / TuChoi) = ngân sách BẤT BIẾN — chặn CẢ Admin (ngay sau
        // load + NotFound, TRƯỚC role-gate). Số NS đã chốt snapshot tại thời điểm duyệt.
        if (pe.Phase is PurchaseEvaluationPhase.DaDuyet or PurchaseEvaluationPhase.TuChoi)
            throw new ConflictException(
                "Phiếu đã kết thúc — ngân sách đã chốt tại thời điểm duyệt, không sửa được nữa.");

        // Fail-closed TRƯỚC mọi side-effect.
        if (!currentUser.Roles.Contains(AppRoles.Admin)
            && !currentUser.Roles.Contains(AppRoles.CostControl))
        {
            throw new ForbiddenException(
                "Chỉ Phòng Kiểm soát Chi phí (CCM) hoặc Admin được nhập NS kỳ này (CCM).");
        }

        var oldVal = pe.CcmBudgetPeriodAmount;
        pe.CcmBudgetPeriodAmount = request.CcmBudgetPeriodAmount;   // absolute-set (null = clear)

        db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
        {
            PurchaseEvaluationId = pe.Id,
            EntityType = PurchaseEvaluationEntityType.Header,
            Action = ChangelogAction.Update,
            PhaseAtChange = pe.Phase,
            UserId = currentUser.UserId,
            UserName = currentUser.FullName ?? currentUser.Email,
            Summary = $"NS kỳ này (CCM): {oldVal?.ToString("N0") ?? "(trống)"}đ → {request.CcmBudgetPeriodAmount?.ToString("N0") ?? "(trống)"}đ",
        });

        await db.SaveChangesAsync(ct);
    }
}
