using FluentValidation;
using MediatR;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Budgets.Dtos;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Common.Models;
using SolutionErp.Application.Notifications;
using SolutionErp.Domain.Budgets;
using SolutionErp.Domain.Common;       // ApprovalStage
using SolutionErp.Domain.Contracts;   // ApprovalDecision + ChangelogAction
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Notifications;

namespace SolutionErp.Application.Budgets;

// Compact CQRS feature file cho module Ngân sách. Pattern simplified vs PE
// (no versioned WF, hardcoded BudgetPolicy.Default). Workflow 5 phase:
// DangSoanThao → ChoCCM → ChoCEO → DaDuyet/TuChoi.

// ========== CREATE ==========

public record CreateBudgetCommand(
    string TenNganSach,
    string? Description,
    int NamNganSach,
    Guid ProjectId,
    Guid? DepartmentId) : IRequest<Guid>;

public class CreateBudgetCommandValidator : AbstractValidator<CreateBudgetCommand>
{
    public CreateBudgetCommandValidator()
    {
        RuleFor(x => x.TenNganSach).NotEmpty().MaximumLength(500);
        RuleFor(x => x.Description).MaximumLength(2000);
        RuleFor(x => x.ProjectId).NotEmpty();
        RuleFor(x => x.NamNganSach).InclusiveBetween(2020, 2100);
    }
}

public class CreateBudgetCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser) : IRequestHandler<CreateBudgetCommand, Guid>
{
    public async Task<Guid> Handle(CreateBudgetCommand request, CancellationToken ct)
    {
        _ = await db.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, ct)
            ?? throw new NotFoundException("Project", request.ProjectId);

        var sla = BudgetPolicies.Default.PhaseSla.GetValueOrDefault(BudgetPhase.DangSoanThao);
        var entity = new Budget
        {
            TenNganSach = request.TenNganSach,
            Description = request.Description,
            NamNganSach = request.NamNganSach,
            ProjectId = request.ProjectId,
            DepartmentId = request.DepartmentId,
            DrafterUserId = currentUser.UserId,
            Phase = BudgetPhase.DangSoanThao,
            TongNganSach = 0,
            SlaDeadline = sla is null ? null : DateTime.UtcNow.Add(sla.Value),
            // Auto-gen MaNganSach đơn giản — atomic sequence sau (Phase 8)
            MaNganSach = $"NS-{DateTime.UtcNow:yyyyMM}-{Random.Shared.Next(1000, 9999)}",
        };
        db.Budgets.Add(entity);

        db.BudgetChangelogs.Add(new BudgetChangelog
        {
            BudgetId = entity.Id,
            EntityType = BudgetEntityType.Header,
            Action = ChangelogAction.Insert,
            PhaseAtChange = entity.Phase,
            UserId = currentUser.UserId,
            Summary = $"Tạo ngân sách {entity.MaNganSach} — {entity.TenNganSach}",
        });

        await db.SaveChangesAsync(ct);
        return entity.Id;
    }
}

// ========== UPDATE draft ==========

public record UpdateBudgetDraftCommand(
    Guid Id,
    string TenNganSach,
    string? Description,
    int NamNganSach) : IRequest;

public class UpdateBudgetDraftCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser) : IRequestHandler<UpdateBudgetDraftCommand>
{
    public async Task Handle(UpdateBudgetDraftCommand request, CancellationToken ct)
    {
        var entity = await db.Budgets.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("Budget", request.Id);
        if (entity.Phase != BudgetPhase.DangSoanThao)
            throw new ConflictException("Chỉ sửa được ngân sách khi Đang soạn thảo.");

        entity.TenNganSach = request.TenNganSach;
        entity.Description = request.Description;
        entity.NamNganSach = request.NamNganSach;

        db.BudgetChangelogs.Add(new BudgetChangelog
        {
            BudgetId = entity.Id,
            EntityType = BudgetEntityType.Header,
            Action = ChangelogAction.Update,
            PhaseAtChange = entity.Phase,
            UserId = currentUser.UserId,
            Summary = "Cập nhật ngân sách",
        });
        await db.SaveChangesAsync(ct);
    }
}

// ========== TRANSITION ==========

public record TransitionBudgetCommand(
    Guid Id, BudgetPhase TargetPhase, ApprovalDecision Decision, string? Comment) : IRequest;

public class TransitionBudgetCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser,
    UserManager<User> userManager,
    INotificationService notifications,
    IDateTime dateTime) : IRequestHandler<TransitionBudgetCommand>
{
    public async Task Handle(TransitionBudgetCommand request, CancellationToken ct)
    {
        if (!currentUser.IsAuthenticated || currentUser.UserId is null)
            throw new UnauthorizedException();

        var entity = await db.Budgets.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("Budget", request.Id);

        // ===== Smart reject + resume (Phase 9 — Migration 16) =====
        var fromPhase = entity.Phase;
        var targetPhase = request.TargetPhase;
        var isResumingAfterReject = request.Decision == ApprovalDecision.Approve
            && fromPhase == BudgetPhase.DangSoanThao
            && entity.RejectedFromPhase != null;

        if (request.Decision == ApprovalDecision.Reject)
        {
            entity.RejectedFromPhase = fromPhase;
            targetPhase = BudgetPhase.DangSoanThao;
        }
        else if (isResumingAfterReject)
        {
            targetPhase = entity.RejectedFromPhase!.Value;
            entity.RejectedFromPhase = null;
        }

        var policy = BudgetPolicies.Default;
        var isAdmin = currentUser.Roles.Contains(AppRoles.Admin);

        // Policy guard — bypass khi resume sau reject.
        if (!isAdmin && !isResumingAfterReject
            && !policy.IsTransitionAllowed(fromPhase, targetPhase, currentUser.Roles))
            throw new ForbiddenException(
                $"Role không đủ quyền chuyển {fromPhase} → {targetPhase}.");

        // ===== 2-stage department approval (Phase 9 — Migration 16) =====
        // Mirror PE/Contract. Low-priority cho Budget vì ít dept duyệt budget,
        // nhưng giữ consistent UX 3 module.
        if (request.Decision == ApprovalDecision.Approve
            && targetPhase != BudgetPhase.DangSoanThao
            && targetPhase != BudgetPhase.TuChoi
            && !isResumingAfterReject
            && !isAdmin
            && currentUser.UserId is Guid actorUid)
        {
            var actor = await userManager.FindByIdAsync(actorUid.ToString());
            if (actor?.DepartmentId is Guid deptId)
            {
                var isManager = currentUser.Roles.Contains(AppRoles.DeptManager);
                var canBypass = actor.CanBypassReview;
                var stage = (isManager || canBypass) ? ApprovalStage.Confirm : ApprovalStage.Review;
                var isBypassed = !isManager && canBypass;
                var roleSnapshot = isManager ? "TPB" : (canBypass ? "NV(bypass)" : "NV");

                var existing = await db.BudgetDepartmentApprovals
                    .FirstOrDefaultAsync(a =>
                        a.BudgetId == entity.Id
                        && a.PhaseAtApproval == (int)fromPhase
                        && a.DepartmentId == deptId
                        && a.Stage == stage, ct);
                if (existing is null)
                {
                    db.BudgetDepartmentApprovals.Add(new BudgetDepartmentApproval
                    {
                        BudgetId = entity.Id,
                        PhaseAtApproval = (int)fromPhase,
                        DepartmentId = deptId,
                        Stage = stage,
                        ApproverUserId = actorUid,
                        ApproverRoleSnapshot = roleSnapshot,
                        Comment = request.Comment,
                        ApprovedAt = dateTime.UtcNow,
                        IsBypassed = isBypassed,
                    });
                }
                else
                {
                    existing.ApproverUserId = actorUid;
                    existing.ApproverRoleSnapshot = roleSnapshot;
                    existing.Comment = request.Comment;
                    existing.ApprovedAt = dateTime.UtcNow;
                    existing.IsBypassed = isBypassed;
                }

                var hasConfirm = stage == ApprovalStage.Confirm
                    || await db.BudgetDepartmentApprovals.AnyAsync(a =>
                        a.BudgetId == entity.Id
                        && a.PhaseAtApproval == (int)fromPhase
                        && a.DepartmentId == deptId
                        && a.Stage == ApprovalStage.Confirm, ct);

                if (!hasConfirm)
                {
                    // BLOCK transition. Log audit Approval + Changelog.
                    db.BudgetApprovals.Add(new BudgetApproval
                    {
                        BudgetId = entity.Id,
                        FromPhase = fromPhase,
                        ToPhase = fromPhase,
                        ApproverUserId = actorUid,
                        Decision = ApprovalDecision.Approve,
                        Comment = $"[Review NV] {request.Comment ?? ""}",
                        ApprovedAt = dateTime.UtcNow,
                    });

                    string? reviewerName = (actor.FullName ?? actor.Email);
                    db.BudgetChangelogs.Add(new BudgetChangelog
                    {
                        BudgetId = entity.Id,
                        EntityType = BudgetEntityType.Workflow,
                        Action = ChangelogAction.Transition,
                        PhaseAtChange = fromPhase,
                        UserId = actorUid,
                        UserName = reviewerName ?? "Hệ thống",
                        Summary = $"{reviewerName} (NV) đã review phase {fromPhase}, chờ TPB confirm",
                        ContextNote = request.Comment,
                    });

                    // Notify TPB cùng dept. Best effort.
                    try
                    {
                        var managers = await db.Users.AsNoTracking()
                            .Where(u => u.DepartmentId == deptId && u.Id != actorUid && u.IsActive)
                            .Select(u => u.Id)
                            .ToListAsync(ct);
                        foreach (var mgrId in managers)
                        {
                            var mgr = await userManager.FindByIdAsync(mgrId.ToString());
                            if (mgr is null) continue;
                            var roles = await userManager.GetRolesAsync(mgr);
                            if (!roles.Contains(AppRoles.DeptManager)) continue;

                            await notifications.NotifyAsync(
                                mgrId,
                                NotificationType.ContractPhaseTransition,
                                title: $"NS {entity.MaNganSach ?? entity.TenNganSach} chờ TPB confirm",
                                description: $"NV {reviewerName} đã review phase {fromPhase}. Vui lòng vào confirm để workflow tiếp tục.",
                                href: $"/budgets/{entity.Id}",
                                refId: entity.Id,
                                ct: ct);
                        }
                    }
                    catch { /* notification fail non-critical */ }

                    await db.SaveChangesAsync(ct);
                    return;
                }
            }
        }

        entity.SlaWarningSent = false;
        entity.Phase = targetPhase;
        var sla = policy.PhaseSla.GetValueOrDefault(targetPhase);
        entity.SlaDeadline = sla is null ? null : DateTime.UtcNow.Add(sla.Value);

        db.BudgetApprovals.Add(new BudgetApproval
        {
            BudgetId = entity.Id,
            FromPhase = fromPhase,
            ToPhase = targetPhase,
            ApproverUserId = currentUser.UserId,
            Decision = request.Decision,
            Comment = request.Comment,
            ApprovedAt = DateTime.UtcNow,
        });

        string? actorName = null;
        if (currentUser.UserId is Guid uid)
        {
            var u = await userManager.FindByIdAsync(uid.ToString());
            actorName = u?.FullName ?? u?.Email;
        }
        db.BudgetChangelogs.Add(new BudgetChangelog
        {
            BudgetId = entity.Id,
            EntityType = BudgetEntityType.Workflow,
            Action = ChangelogAction.Transition,
            PhaseAtChange = targetPhase,
            UserId = currentUser.UserId,
            UserName = actorName ?? "Hệ thống",
            Summary = $"Chuyển phase {fromPhase} → {targetPhase}",
            ContextNote = request.Comment,
        });
        await db.SaveChangesAsync(ct);
    }
}

// ========== LIST ==========

public record ListBudgetsQuery(
    BudgetPhase? Phase = null,
    Guid? ProjectId = null,
    int? NamNganSach = null) : PagedRequest, IRequest<PagedResult<BudgetListItemDto>>;

public class ListBudgetsQueryHandler(
    IApplicationDbContext db) : IRequestHandler<ListBudgetsQuery, PagedResult<BudgetListItemDto>>
{
    public async Task<PagedResult<BudgetListItemDto>> Handle(ListBudgetsQuery request, CancellationToken ct)
    {
        var q = from e in db.Budgets.AsNoTracking()
                join p in db.Projects.AsNoTracking() on e.ProjectId equals p.Id
                select new { e, p };

        if (request.Phase is not null) q = q.Where(x => x.e.Phase == request.Phase);
        if (request.ProjectId is not null) q = q.Where(x => x.e.ProjectId == request.ProjectId);
        if (request.NamNganSach is not null) q = q.Where(x => x.e.NamNganSach == request.NamNganSach);
        if (!string.IsNullOrWhiteSpace(request.Search))
        {
            var s = request.Search.Trim();
            q = q.Where(x => (x.e.MaNganSach != null && x.e.MaNganSach.Contains(s))
                || x.e.TenNganSach.Contains(s) || x.p.Name.Contains(s));
        }
        q = request.SortDesc ? q.OrderByDescending(x => x.e.CreatedAt) : q.OrderBy(x => x.e.CreatedAt);

        var total = await q.CountAsync(ct);
        var items = await q.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
            .Select(x => new BudgetListItemDto(
                x.e.Id, x.e.MaNganSach, x.e.TenNganSach, x.e.NamNganSach, x.e.Phase,
                x.e.ProjectId, x.p.Name, x.e.TongNganSach, x.e.SlaDeadline, x.e.CreatedAt))
            .ToListAsync(ct);
        return new PagedResult<BudgetListItemDto>(items, total, request.Page, request.PageSize);
    }
}

// ========== GET detail bundle ==========

public record GetBudgetQuery(Guid Id) : IRequest<BudgetDetailBundleDto>;

public class GetBudgetQueryHandler(
    IApplicationDbContext db,
    UserManager<User> userManager) : IRequestHandler<GetBudgetQuery, BudgetDetailBundleDto>
{
    public async Task<BudgetDetailBundleDto> Handle(GetBudgetQuery request, CancellationToken ct)
    {
        var e = await db.Budgets.AsNoTracking()
            .Include(x => x.Details)
            .Include(x => x.Approvals)
            .FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("Budget", request.Id);

        var project = await db.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == e.ProjectId, ct);
        var department = e.DepartmentId is null ? null : await db.Departments.AsNoTracking()
            .FirstOrDefaultAsync(d => d.Id == e.DepartmentId, ct);

        var userIds = new HashSet<Guid>();
        if (e.DrafterUserId is Guid did) userIds.Add(did);
        foreach (var a in e.Approvals) if (a.ApproverUserId is Guid aid) userIds.Add(aid);
        var users = await userManager.Users.AsNoTracking()
            .Where(u => userIds.Contains(u.Id))
            .ToDictionaryAsync(u => u.Id, u => u.FullName, ct);

        var policy = BudgetPolicies.Default;

        return new BudgetDetailBundleDto(
            e.Id, e.MaNganSach, e.TenNganSach, e.Description, e.NamNganSach, e.Phase,
            e.ProjectId, project?.Name ?? "",
            e.DepartmentId, department?.Name,
            e.DrafterUserId, e.DrafterUserId is Guid d && users.TryGetValue(d, out var dn) ? dn : null,
            e.TongNganSach, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,
            e.Details.OrderBy(d => d.Order).Select(d => new BudgetDetailDto(
                d.Id, d.GroupCode, d.GroupName, d.ItemCode, d.NoiDung, d.DonViTinh,
                d.KhoiLuong, d.DonGia, d.ThanhTien, d.Order, d.GhiChu)).ToList(),
            e.Approvals.OrderBy(a => a.ApprovedAt).Select(a => new BudgetApprovalDto(
                a.Id, a.FromPhase, a.ToPhase, a.ApproverUserId,
                a.ApproverUserId is Guid uid && users.TryGetValue(uid, out var an) ? an : null,
                a.Decision, a.Comment, a.ApprovedAt)).ToList(),
            new BudgetWorkflowSummaryDto(
                policy.Name, policy.Description,
                policy.ActivePhases.ToList(),
                policy.NextPhasesFrom(e.Phase).ToList()));
    }
}

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

public record DeleteBudgetCommand(Guid Id) : IRequest;

public class DeleteBudgetCommandHandler(
    IApplicationDbContext db) : IRequestHandler<DeleteBudgetCommand>
{
    public async Task Handle(DeleteBudgetCommand request, CancellationToken ct)
    {
        var entity = await db.Budgets.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("Budget", request.Id);
        if (entity.Phase != BudgetPhase.DangSoanThao && entity.Phase != BudgetPhase.TuChoi)
            throw new ConflictException("Chỉ xóa được ngân sách phase Soạn thảo / Từ chối.");
        db.Budgets.Remove(entity);
        await db.SaveChangesAsync(ct);
    }
}

// ========== Detail CRUD ==========

public record AddBudgetDetailCommand(
    Guid BudgetId, string GroupCode, string GroupName, string? ItemCode, string NoiDung,
    string? DonViTinh, decimal KhoiLuong, decimal DonGia, decimal ThanhTien, string? GhiChu) : IRequest<Guid>;

public class AddBudgetDetailCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser) : IRequestHandler<AddBudgetDetailCommand, Guid>
{
    public async Task<Guid> Handle(AddBudgetDetailCommand request, CancellationToken ct)
    {
        var bg = await db.Budgets.FirstOrDefaultAsync(x => x.Id == request.BudgetId, ct)
            ?? throw new NotFoundException("Budget", request.BudgetId);
        // Lock edit guard (Phase 9 — Migration 16)
        if (bg.Phase != BudgetPhase.DangSoanThao)
            throw new ConflictException($"Ngân sách đã trình duyệt (Phase={bg.Phase}), không thể thêm hạng mục. Phải reject để Drafter sửa lại.");
        var maxOrder = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id)
            .Select(d => (int?)d.Order).MaxAsync(ct);
        var entity = new BudgetDetail
        {
            BudgetId = bg.Id,
            GroupCode = request.GroupCode, GroupName = request.GroupName,
            ItemCode = request.ItemCode, NoiDung = request.NoiDung, DonViTinh = request.DonViTinh,
            KhoiLuong = request.KhoiLuong, DonGia = request.DonGia, ThanhTien = request.ThanhTien,
            GhiChu = request.GhiChu, Order = (maxOrder ?? 0) + 1,
        };
        db.BudgetDetails.Add(entity);

        // Recompute TongNganSach
        bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id)
            .SumAsync(d => d.ThanhTien, ct) + entity.ThanhTien;

        db.BudgetChangelogs.Add(new BudgetChangelog
        {
            BudgetId = bg.Id, EntityType = BudgetEntityType.Detail, EntityId = entity.Id,
            Action = ChangelogAction.Insert, PhaseAtChange = bg.Phase,
            UserId = currentUser.UserId,
            Summary = $"Thêm hạng mục {request.GroupCode} — {request.NoiDung}",
        });
        await db.SaveChangesAsync(ct);
        return entity.Id;
    }
}

public record UpdateBudgetDetailCommand(
    Guid BudgetId, Guid DetailId, string GroupCode, string GroupName, string? ItemCode, string NoiDung,
    string? DonViTinh, decimal KhoiLuong, decimal DonGia, decimal ThanhTien, string? GhiChu) : IRequest;

public class UpdateBudgetDetailCommandHandler(
    IApplicationDbContext db) : IRequestHandler<UpdateBudgetDetailCommand>
{
    public async Task Handle(UpdateBudgetDetailCommand request, CancellationToken ct)
    {
        var bg = await db.Budgets.FirstOrDefaultAsync(b => b.Id == request.BudgetId, ct)
            ?? throw new NotFoundException("Budget", request.BudgetId);
        // Lock edit guard (Phase 9 — Migration 16)
        if (bg.Phase != BudgetPhase.DangSoanThao)
            throw new ConflictException($"Ngân sách đã trình duyệt (Phase={bg.Phase}), không thể sửa hạng mục. Phải reject để Drafter sửa lại.");
        var entity = await db.BudgetDetails
            .FirstOrDefaultAsync(d => d.Id == request.DetailId && d.BudgetId == request.BudgetId, ct)
            ?? throw new NotFoundException("BudgetDetail", request.DetailId);
        entity.GroupCode = request.GroupCode; entity.GroupName = request.GroupName;
        entity.ItemCode = request.ItemCode; entity.NoiDung = request.NoiDung; entity.DonViTinh = request.DonViTinh;
        entity.KhoiLuong = request.KhoiLuong; entity.DonGia = request.DonGia; entity.ThanhTien = request.ThanhTien;
        entity.GhiChu = request.GhiChu;

        bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id)
            .SumAsync(d => d.ThanhTien, ct);

        await db.SaveChangesAsync(ct);
    }
}

public record DeleteBudgetDetailCommand(Guid BudgetId, Guid DetailId) : IRequest;

public class DeleteBudgetDetailCommandHandler(
    IApplicationDbContext db) : IRequestHandler<DeleteBudgetDetailCommand>
{
    public async Task Handle(DeleteBudgetDetailCommand request, CancellationToken ct)
    {
        var bg = await db.Budgets.FirstOrDefaultAsync(b => b.Id == request.BudgetId, ct)
            ?? throw new NotFoundException("Budget", request.BudgetId);
        // Lock edit guard (Phase 9 — Migration 16)
        if (bg.Phase != BudgetPhase.DangSoanThao)
            throw new ConflictException($"Ngân sách đã trình duyệt (Phase={bg.Phase}), không thể xóa hạng mục. Phải reject để Drafter sửa lại.");
        var entity = await db.BudgetDetails
            .FirstOrDefaultAsync(d => d.Id == request.DetailId && d.BudgetId == request.BudgetId, ct)
            ?? throw new NotFoundException("BudgetDetail", request.DetailId);
        db.BudgetDetails.Remove(entity);

        bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id && d.Id != entity.Id)
            .SumAsync(d => d.ThanhTien, ct);

        await db.SaveChangesAsync(ct);
    }
}

// ========== CHANGELOG list ==========

public record ListBudgetChangelogsQuery(Guid BudgetId, int Take = 200) : IRequest<List<BudgetChangelogDto>>;

public class ListBudgetChangelogsQueryHandler(IApplicationDbContext db)
    : IRequestHandler<ListBudgetChangelogsQuery, List<BudgetChangelogDto>>
{
    public async Task<List<BudgetChangelogDto>> Handle(ListBudgetChangelogsQuery request, CancellationToken ct) =>
        await db.BudgetChangelogs.AsNoTracking()
            .Where(c => c.BudgetId == request.BudgetId)
            .OrderByDescending(c => c.CreatedAt)
            .Take(request.Take)
            .Select(c => new BudgetChangelogDto(
                c.Id, c.EntityType, c.EntityId, c.Action, c.PhaseAtChange,
                c.UserId, c.UserName, c.Summary, c.FieldChangesJson, c.ContextNote, c.CreatedAt))
            .ToListAsync(ct);
}
