using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Contracts.Services;
using SolutionErp.Application.Notifications;
using SolutionErp.Domain.Common;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Notifications;

namespace SolutionErp.Infrastructure.Services;

// Contract Workflow Service — Session 17 spec mới (state machine 5 trạng thái):
//   Nháp (DangSoanThao) ──trình──► Đã gửi duyệt (ChoDuyet) ─approve cấp cuối─► Đã phát hành (DaPhatHanh, terminal + gen mã)
//                                                          ├─ Trả lại ────────► Trả lại (TraLai)
//                                                          └─ Từ chối ────────► Từ chối (TuChoi, terminal)
//   Trả lại ──Drafter sửa+gửi lại──► Đã gửi duyệt (chạy LẠI từ đầu, KHÔNG jump-back)
//
// Khác Mig 21 (Session 16): bỏ smart-reject jump-back. Trả lại = Phase RIÊNG
// (TraLai=98), không revert về DangSoanThao. Drafter từ TraLai gửi lại như
// case Nháp — workflow chạy lại từ Cấp 1 Bước 1.
public class ContractWorkflowService(
    IApplicationDbContext db,
    IContractCodeGenerator codeGenerator,
    IDateTime dateTime,
    INotificationService notifications,
    IChangelogService changelog,
    UserManager<User> userManager) : IContractWorkflowService
{
    public TimeSpan? GetPhaseSla(ContractPhase phase) =>
        phase == ContractPhase.ChoDuyet ? TimeSpan.FromDays(7) : null;

    public async Task TransitionAsync(
        Contract contract,
        ContractPhase targetPhase,
        Guid? actorUserId,
        IReadOnlyList<string> actorRoles,
        ApprovalDecision decision,
        string? comment,
        bool skipToFinal = false,
        CancellationToken ct = default)
    {
        var fromPhase = contract.Phase;
        var isAdmin = actorRoles.Contains(AppRoles.Admin);
        var isSystem = actorUserId is null && decision == ApprovalDecision.AutoApprove;

        // ===== REJECT BRANCH =====
        if (decision == ApprovalDecision.Reject)
        {
            if (targetPhase == ContractPhase.TuChoi)
            {
                contract.Phase = ContractPhase.TuChoi;
            }
            else
            {
                // Trả lại — Phase=TraLai RIÊNG (không revert về DangSoanThao).
                // Drafter sửa từ TraLai rồi gửi lại sẽ chạy lại từ Cấp 1 Bước 1.
                contract.Phase = ContractPhase.TraLai;
                contract.CurrentWorkflowStepIndex = null;
            }
            contract.SlaDeadline = null;
            await LogTransitionAsync(contract, fromPhase, contract.Phase, actorUserId, decision, comment, ct);
            await db.SaveChangesAsync(ct);
            return;
        }

        // ===== DRAFTER TRÌNH/GỬI LẠI (Nháp HOẶC Trả lại → ChoDuyet) =====
        // Cả 2 entry point cùng logic: chạy lại từ đầu (CurrentWorkflowStepIndex=0).
        if ((fromPhase == ContractPhase.DangSoanThao || fromPhase == ContractPhase.TraLai)
            && (targetPhase == ContractPhase.ChoDuyet || (!isAdmin && !isSystem)))
        {
            // [W6 S161] Nới vai được TRÌNH: `Drafter ∨ DeptManager ∨ người-tạo ∨ Procurement`.
            // Lỗ đo 07-29: nhân sự Phòng Mua hàng (role `Procurement`) bị 403 khi trình HĐ do
            // chính mình lập. Vẫn giữ 403 cho role NGOÀI 4 vế này.
            // 🔴 `actorUserId is Guid` là BẮT BUỘC, không được viết thẳng
            // `contract.CreatedBy == actorUserId`: cả hai đều `Guid?` ⇒ HĐ seed/legacy có
            // `CreatedBy == null` gặp actor null sẽ ra `null == null` = TRUE và lọt guard.
            // Xét cả `DrafterUserId` vì đó là field handler tạo HĐ gán tường minh
            // (`ContractFeatures.cs:96`), còn `CreatedBy` do `AuditingInterceptor.cs:45` gán —
            // HĐ nạp từ seed có thể trống ô sau.
            var isCreator = actorUserId is Guid actorId
                && (contract.CreatedBy == actorId || contract.DrafterUserId == actorId);
            if (!isAdmin && !isSystem
                && !actorRoles.Contains(AppRoles.Drafter)
                && !actorRoles.Contains(AppRoles.DeptManager)
                && !actorRoles.Contains(AppRoles.Procurement)
                && !isCreator)
            {
                throw new ForbiddenException(
                    $"Role ({string.Join(",", actorRoles)}) không đủ quyền trình duyệt HĐ.");
            }
            contract.Phase = ContractPhase.ChoDuyet;
            contract.CurrentWorkflowStepIndex = 0;
            // [Plan B S29 2026-05-22] V2 pointer init — mirror PE line 153.
            // Chỉ init levelOrder=1 nếu pin schema V2 (ApprovalWorkflowId set).
            contract.CurrentApprovalLevelOrder = contract.ApprovalWorkflowId is not null ? 1 : null;
            contract.SlaDeadline = dateTime.UtcNow.AddDays(7);
            await LogTransitionAsync(contract, fromPhase, ContractPhase.ChoDuyet, actorUserId, decision, comment, ct);
            await db.SaveChangesAsync(ct);
            return;
        }

        // ===== APPROVE STEP =====
        if (fromPhase == ContractPhase.ChoDuyet && decision == ApprovalDecision.Approve)
        {
            // [Plan B S29 2026-05-22] Branch V2 schema mới (ApprovalWorkflowId pin)
            // vs V1 legacy (WorkflowDefinitionId pin Mig 21). Mirror PE
            // PurchaseEvaluationWorkflowService.cs line 161-180 pattern.
            // V1 legacy giữ behavior cũ — 7 prod contract chạy nhánh này.
            if (contract.ApprovalWorkflowId is Guid awId)
            {
                await ApproveV2Async(contract, awId, actorUserId, actorRoles, isAdmin, isSystem, comment, skipToFinal, ct);
                await db.SaveChangesAsync(ct);
                return;
            }
            if (skipToFinal && !isAdmin && !isSystem)
                throw new ConflictException(
                    "skipToFinal chỉ hỗ trợ HĐ V2 (ApprovalWorkflowsV2). HĐ V1 legacy không có per-Approver-slot flag.");

            var def = contract.WorkflowDefinitionId is Guid wfId
                ? await db.WorkflowDefinitions.AsNoTracking()
                    .Include(d => d.Steps.OrderBy(s => s.Order))
                        .ThenInclude(s => s.Approvers)
                    .FirstOrDefaultAsync(d => d.Id == wfId, ct)
                : null;

            if (def == null || def.Steps.Count == 0)
                throw new ConflictException("HĐ chưa pin workflow definition hoặc workflow không có step.");

            var steps = def.Steps.OrderBy(s => s.Order).ToList();
            var currentIdx = contract.CurrentWorkflowStepIndex ?? 0;
            if (currentIdx < 0 || currentIdx >= steps.Count)
                throw new ConflictException($"CurrentWorkflowStepIndex={currentIdx} không hợp lệ.");

            var currentStep = steps[currentIdx];

            if (!isAdmin && !isSystem)
            {
                var actor = actorUserId is Guid uid ? await userManager.FindByIdAsync(uid.ToString()) : null;
                if (actor == null)
                    throw new ForbiddenException("Không xác định được approver.");

                var matchByDeptLevel = currentStep.DepartmentId != null
                    && currentStep.PositionLevel != null
                    && actor.DepartmentId == currentStep.DepartmentId
                    && actor.PositionLevel != null
                    && (int)actor.PositionLevel >= (int)currentStep.PositionLevel;

                var matchByExplicitUser = currentStep.Approvers.Any(a =>
                    a.Kind == WorkflowApproverKind.User
                    && Guid.TryParse(a.AssignmentValue, out var auid)
                    && auid == actor.Id);

                var matchByRole = currentStep.Approvers.Any(a =>
                    a.Kind == WorkflowApproverKind.Role
                    && actorRoles.Contains(a.AssignmentValue));

                if (!matchByDeptLevel && !matchByExplicitUser && !matchByRole)
                    throw new ForbiddenException(
                        $"Step {currentIdx + 1} ({currentStep.Name}) yêu cầu phòng={currentStep.DepartmentId}, cấp={currentStep.PositionLevel}. Bạn không khớp.");
            }

            db.ContractApprovals.Add(new ContractApproval
            {
                ContractId = contract.Id,
                FromPhase = fromPhase,
                ToPhase = fromPhase,
                ApproverUserId = actorUserId,
                Decision = decision,
                Comment = $"[Step {currentIdx + 1}] {comment ?? ""}",
                ApprovedAt = dateTime.UtcNow,
            });

            var nextIdx = currentIdx + 1;
            if (nextIdx >= steps.Count)
            {
                // All steps done — gen mã HĐ + DaPhatHanh
                if (string.IsNullOrEmpty(contract.MaHopDong))
                {
                    var supplier = await db.Suppliers.FirstOrDefaultAsync(s => s.Id == contract.SupplierId, ct)
                        ?? throw new NotFoundException("Supplier", contract.SupplierId);
                    var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == contract.ProjectId, ct)
                        ?? throw new NotFoundException("Project", contract.ProjectId);
                    contract.MaHopDong = await codeGenerator.GenerateAsync(contract, project.Code, supplier.Code, ct);
                }
                contract.Phase = ContractPhase.DaPhatHanh;
                contract.CurrentWorkflowStepIndex = null;
                contract.SlaDeadline = null;
                await LogTransitionAsync(contract, fromPhase, ContractPhase.DaPhatHanh, actorUserId, decision, comment, ct);
            }
            else
            {
                contract.CurrentWorkflowStepIndex = nextIdx;
                contract.SlaDeadline = dateTime.UtcNow.AddDays(7);
                await LogTransitionAsync(contract, fromPhase, fromPhase, actorUserId, decision,
                    $"Hoàn tất step {currentIdx + 1}/{steps.Count}, sang step {nextIdx + 1}", ct);
            }
            await db.SaveChangesAsync(ct);
            return;
        }

        // Admin manual override
        if (isAdmin)
        {
            contract.Phase = targetPhase;
            contract.SlaDeadline = targetPhase == ContractPhase.ChoDuyet
                ? dateTime.UtcNow.AddDays(7) : null;
            await LogTransitionAsync(contract, fromPhase, targetPhase, actorUserId, decision, comment, ct);
            await db.SaveChangesAsync(ct);
            return;
        }

        throw new ConflictException($"Transition {fromPhase} → {targetPhase} không hỗ trợ.");
    }

    // ===== V2 APPROVE (Mig 32+33 — Plan B S29 2026-05-22) =====
    // Mirror PurchaseEvaluationWorkflowService.cs:ApproveV2Async (line 446-634).
    // Khác PE: terminal hoàn tất → gen mã HĐ + Phase=DaPhatHanh (PE chỉ
    // Phase=DaDuyet, không gen mã). V1 legacy giữ behavior cũ.
    //
    // skipToFinal F2 (Mig 31 Plan K S23): Approver tick "Duyệt thẳng Cấp cuối"
    // + admin opt-in per slot tại matchingLevel.AllowApproverSkipToFinal → bỏ
    // qua mọi Bước/Cấp trung gian, advance pointer tới Bước cuối + Cấp cuối.
    // Phase giữ ChoDuyet — NV cuối vẫn duyệt thật để tiến DaPhatHanh.
    //
    // TODO Chunk C: UPSERT ContractLevelOpinion (table chưa tồn tại — Mig 33
    // sẽ scaffold + entity + EF config). Sau Chunk C done, em main add block
    // UPSERT mirror PE line 512-546.
    private async Task ApproveV2Async(
        Contract contract,
        Guid awId,
        Guid? actorUserId,
        IReadOnlyList<string> actorRoles,
        bool isAdmin,
        bool isSystem,
        string? comment,
        bool skipToFinal,
        CancellationToken ct)
    {
        var aw = await db.ApprovalWorkflows.AsNoTracking()
            .Include(w => w.Steps.OrderBy(s => s.Order))
                .ThenInclude(s => s.Levels.OrderBy(l => l.Order))
            .FirstOrDefaultAsync(w => w.Id == awId, ct)
            ?? throw new ConflictException($"ApprovalWorkflow {awId} không tồn tại.");

        var steps = aw.Steps.OrderBy(s => s.Order).ToList();
        if (steps.Count == 0)
            throw new ConflictException("Quy trình chưa có bước nào.");

        var currentIdx = contract.CurrentWorkflowStepIndex ?? 0;
        if (currentIdx < 0 || currentIdx >= steps.Count)
            throw new ConflictException($"CurrentWorkflowStepIndex={currentIdx} không hợp lệ (max={steps.Count - 1}).");

        var currentLevelOrder = contract.CurrentApprovalLevelOrder ?? 1;
        var currentStep = steps[currentIdx];

        // Group levels by Order = Cấp. Mỗi Cấp có N approvers (OR-of-N).
        var levelGroups = currentStep.Levels.OrderBy(l => l.Order).GroupBy(l => l.Order).ToList();
        var maxLevelOrder = levelGroups.Count == 0 ? 0 : levelGroups.Max(g => g.Key);
        if (currentLevelOrder < 1 || currentLevelOrder > maxLevelOrder)
            throw new ConflictException($"CurrentApprovalLevelOrder={currentLevelOrder} không hợp lệ (max={maxLevelOrder}).");

        var pendingLevelGroup = levelGroups.FirstOrDefault(g => g.Key == currentLevelOrder)
            ?? throw new ConflictException($"Bước {currentIdx + 1} không có cấp {currentLevelOrder}.");

        // Match approver: actor.Id ∈ pendingLevelGroup.ApproverUserId. Admin bypass.
        if (!isAdmin && !isSystem)
        {
            if (actorUserId is null)
                throw new ForbiddenException("Không xác định được approver.");
            var allowedUserIds = pendingLevelGroup.Select(l => l.ApproverUserId).ToHashSet();
            if (!allowedUserIds.Contains(actorUserId.Value))
            {
                var names = string.Join(", ", allowedUserIds);
                throw new ForbiddenException(
                    $"Bước {currentIdx + 1} ({currentStep.Name}) — Cấp {currentLevelOrder}: bạn không có trong danh sách NV duyệt ({names}).");
            }
        }

        // Log approval. Enrich comment với prefix "[Duyệt vượt cấp]" khi
        // skipToFinal=true (mirror PE Plan AC S25 Bug 3b).
        var skipPrefix = skipToFinal ? "[Duyệt vượt cấp tới Cấp cuối] " : "";
        db.ContractApprovals.Add(new ContractApproval
        {
            ContractId = contract.Id,
            FromPhase = contract.Phase,
            ToPhase = contract.Phase,
            ApproverUserId = actorUserId,
            Decision = ApprovalDecision.Approve,
            Comment = $"{skipPrefix}[Bước {currentIdx + 1} — Cấp {currentLevelOrder}] {comment ?? ""}".Trim(),
            ApprovedAt = dateTime.UtcNow,
        });

        // [Plan B Chunk B2 S29 2026-05-22] UPSERT ContractLevelOpinion vào row
        // Level chính chủ (mirror PE Mig 26 line 512-546). Section 5 FE render
        // dynamic theo flow.steps[].levels[]. Comment khi duyệt auto sync sang
        // Section 5 (read-only summary). Empty comment → "(duyệt — không ý kiến)"
        // placeholder. Multi-NV cùng Cấp (OR-of-N): match level theo
        // ApproverUserId. Admin override → fallback first level group; FE detect
        // SignedByUserId !== Level.ApproverUserId → banner "Admin duyệt thay".
        var matchingLevel = pendingLevelGroup.FirstOrDefault(l => actorUserId.HasValue && l.ApproverUserId == actorUserId.Value)
                            ?? pendingLevelGroup.First();
        var actorFullName = await ResolveActorFullNameAsync(actorUserId, isSystem, ct);
        var existingOpinion = await db.ContractLevelOpinions
            .FirstOrDefaultAsync(o => o.ContractId == contract.Id
                && o.ApprovalWorkflowLevelId == matchingLevel.Id, ct);
        var normalizedComment = string.IsNullOrWhiteSpace(comment)
            ? "(duyệt — không ý kiến)"
            : comment.Trim();
        if (existingOpinion is null)
        {
            db.ContractLevelOpinions.Add(new ContractLevelOpinion
            {
                ContractId = contract.Id,
                ApprovalWorkflowLevelId = matchingLevel.Id,
                Comment = normalizedComment,
                SignedAt = dateTime.UtcNow,
                SignedByUserId = actorUserId ?? Guid.Empty,
                SignedByFullName = actorFullName,
            });
        }
        else
        {
            existingOpinion.Comment = normalizedComment;
            existingOpinion.SignedAt = dateTime.UtcNow;
            existingOpinion.SignedByUserId = actorUserId ?? Guid.Empty;
            existingOpinion.SignedByFullName = actorFullName;
        }

        // skipToFinal F2 (Mig 31 Plan K S23) — Approver scope ChoDuyet skip
        // thẳng Cấp cuối. Admin opt-in per slot tại AllowApproverSkipToFinal.
        if (skipToFinal)
        {
            if (!isAdmin && !isSystem && !matchingLevel.AllowApproverSkipToFinal)
            {
                throw new ConflictException(
                    $"Cấp Approver hiện tại (Bước {currentIdx + 1} Cấp {currentLevelOrder}) " +
                    "chưa được phép duyệt thẳng Cấp cuối. Admin phải tick checkbox " +
                    "'Duyệt thẳng Cấp cuối' trong Workflow Designer cho slot này.");
            }

            var lastStepIdx = steps.Count - 1;
            var lastStep = steps[lastStepIdx];
            var lastLevelGroups = lastStep.Levels.OrderBy(l => l.Order).GroupBy(l => l.Order).ToList();
            var lastLevelMaxOrder = lastLevelGroups.Count == 0 ? 1 : lastLevelGroups.Max(g => g.Key);

            // Guard: actor đã ở Cấp cuối Bước cuối → fall through normal advance
            // (sẽ hit branch nextIdx >= steps.Count → DaPhatHanh đúng).
            if (!(currentIdx == lastStepIdx && currentLevelOrder == lastLevelMaxOrder))
            {
                contract.CurrentWorkflowStepIndex = lastStepIdx;
                contract.CurrentApprovalLevelOrder = lastLevelMaxOrder;
                contract.SlaDeadline = dateTime.UtcNow.AddDays(7);
                await LogTransitionAsync(
                    contract,
                    ContractPhase.ChoDuyet,
                    ContractPhase.ChoDuyet,
                    actorUserId,
                    ApprovalDecision.Approve,
                    $"[Approver skip thẳng tới Bước {lastStepIdx + 1} Cấp {lastLevelMaxOrder} (NV cuối) — bỏ qua các Bước/Cấp trung gian] {comment ?? ""}".Trim(),
                    ct);
                return;
            }
        }

        // Advance: nếu còn cấp tiếp trong Step → levelOrder++; else → next Step + level 1
        if (currentLevelOrder < maxLevelOrder)
        {
            contract.CurrentApprovalLevelOrder = currentLevelOrder + 1;
            contract.SlaDeadline = dateTime.UtcNow.AddDays(7);
            await LogTransitionAsync(contract, contract.Phase, contract.Phase, actorUserId, ApprovalDecision.Approve,
                $"Hoàn tất Cấp {currentLevelOrder}, sang Cấp {currentLevelOrder + 1} cùng Bước {currentIdx + 1}", ct);
            return;
        }

        // Hết cấp trong Step — sang Step kế (Cấp 1)
        var nextIdx = currentIdx + 1;
        if (nextIdx >= steps.Count)
        {
            // All Steps done — terminal DaPhatHanh. Khác PE: phải gen mã HĐ
            // theo RG-001 (mirror V1 line 148-155). FE sau khi nhận DaPhatHanh
            // sẽ refresh hiển thị MaHopDong.
            if (string.IsNullOrEmpty(contract.MaHopDong))
            {
                var supplier = await db.Suppliers.FirstOrDefaultAsync(s => s.Id == contract.SupplierId, ct)
                    ?? throw new NotFoundException("Supplier", contract.SupplierId);
                var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == contract.ProjectId, ct)
                    ?? throw new NotFoundException("Project", contract.ProjectId);
                contract.MaHopDong = await codeGenerator.GenerateAsync(contract, project.Code, supplier.Code, ct);
            }
            contract.Phase = ContractPhase.DaPhatHanh;
            contract.CurrentWorkflowStepIndex = null;
            contract.CurrentApprovalLevelOrder = null;
            contract.SlaDeadline = null;
            await LogTransitionAsync(contract, ContractPhase.ChoDuyet, ContractPhase.DaPhatHanh,
                actorUserId, ApprovalDecision.Approve, comment, ct);
        }
        else
        {
            contract.CurrentWorkflowStepIndex = nextIdx;
            contract.CurrentApprovalLevelOrder = 1;
            contract.SlaDeadline = dateTime.UtcNow.AddDays(7);
            await LogTransitionAsync(contract, contract.Phase, contract.Phase, actorUserId, ApprovalDecision.Approve,
                $"Hoàn tất Bước {currentIdx + 1}/{steps.Count}, sang Bước {nextIdx + 1} (Cấp 1)", ct);
        }
    }

    private async Task LogTransitionAsync(
        Contract contract,
        ContractPhase fromPhase,
        ContractPhase toPhase,
        Guid? actorUserId,
        ApprovalDecision decision,
        string? comment,
        CancellationToken ct)
    {
        await changelog.LogWorkflowTransitionAsync(contract.Id, fromPhase, toPhase, comment);

        if (contract.DrafterUserId is Guid drafterId && drafterId != actorUserId)
        {
            var (title, type) = toPhase switch
            {
                ContractPhase.DaPhatHanh => ($"HĐ {contract.MaHopDong ?? contract.TenHopDong} đã phát hành",
                                              NotificationType.ContractPublished),
                ContractPhase.TuChoi => ($"HĐ {contract.TenHopDong ?? "của bạn"} bị từ chối",
                                          NotificationType.ContractRejected),
                ContractPhase.TraLai when fromPhase == ContractPhase.ChoDuyet =>
                    ($"HĐ {contract.TenHopDong ?? "của bạn"} bị trả lại — vui lòng sửa và trình lại",
                     NotificationType.ContractRejected),
                _ => ($"HĐ {contract.TenHopDong ?? contract.MaHopDong ?? ""} chuyển phase mới",
                      NotificationType.ContractPhaseTransition),
            };
            await notifications.NotifyAsync(
                drafterId, type, title,
                description: $"{fromPhase} → {toPhase}" + (string.IsNullOrWhiteSpace(comment) ? "" : $" · {comment}"),
                href: $"/contracts/{contract.Id}",
                refId: contract.Id,
                ct: ct);
        }
    }

    // [Plan B Chunk B2 S29 2026-05-22] Resolve actor full name for
    // ContractLevelOpinion.SignedByFullName denormalized field (Section 5 FE
    // display). Mirror PE PurchaseEvaluationWorkflowService.cs:774-783.
    private async Task<string> ResolveActorFullNameAsync(Guid? actorUserId, bool isSystem, CancellationToken ct)
    {
        if (isSystem || actorUserId is null) return "(System)";
        var user = await db.Users.AsNoTracking()
            .Where(u => u.Id == actorUserId.Value)
            .Select(u => new { u.FullName, u.UserName })
            .FirstOrDefaultAsync(ct);
        if (user is null) return "(unknown)";
        return !string.IsNullOrWhiteSpace(user.FullName) ? user.FullName : (user.UserName ?? "(unknown)");
    }
}
