using SolutionErp.Domain.Identity;

namespace SolutionErp.Domain.Contracts;

// Encapsulates the workflow rules for one class of contracts — which phases
// apply, who can trigger each transition, and how long each phase gets.
//
// A WorkflowPolicy is paired with one or more ContractType values via
// WorkflowPolicyRegistry. This keeps logic pure + testable and avoids the
// "god dict" problem the hardcoded ContractWorkflowService had.
//
// Domain-layer class (not DB-backed yet) — admin can edit per-contract
// overrides at the instance level via Contract.BypassProcurementAndCCM;
// iteration 2 will make the whole thing DB-backed + admin-editable.
public sealed record WorkflowPolicy(
    string Name,
    string Description,
    IReadOnlyDictionary<(ContractPhase From, ContractPhase To), string[]> Transitions,
    IReadOnlyDictionary<ContractPhase, TimeSpan?> PhaseSla,
    IReadOnlyList<ContractPhase> ActivePhases,
    // User-kind approvers per transition — ngoài Role check, nếu actor's UserId
    // có trong list này → cũng pass guard. Default empty cho 2 policy hardcoded
    // (Standard/SkipCcm), populated qua FromDefinition khi WorkflowStepApprover
    // có Kind=User.
    IReadOnlyDictionary<(ContractPhase From, ContractPhase To), string[]>? UserTransitions = null)
{
    public bool HasPhase(ContractPhase phase) => ActivePhases.Contains(phase);

    public bool IsTransitionAllowed(
        ContractPhase from, ContractPhase to,
        IReadOnlyList<string> actorRoles, Guid? actorUserId = null)
    {
        if (!Transitions.TryGetValue((from, to), out var roles)) return false;
        if (actorRoles.Any(r => roles.Contains(r))) return true;

        // User-kind fallback: nếu actor user ID match explicit user approver
        if (actorUserId is null) return false;
        if (UserTransitions is null) return false;
        if (!UserTransitions.TryGetValue((from, to), out var userIds)) return false;
        var userIdStr = actorUserId.Value.ToString();
        return userIds.Contains(userIdStr);
    }

    public IReadOnlyList<ContractPhase> NextPhasesFrom(ContractPhase from) =>
        Transitions.Keys.Where(k => k.From == from).Select(k => k.To).Distinct().ToList();
}

public static class WorkflowPolicies
{
    // ===== Shared SLA defaults — same across policies. Override per-policy
    // if a class of contracts should move faster/slower. =====
    private static readonly Dictionary<ContractPhase, TimeSpan?> DefaultSla = new()
    {
        [ContractPhase.DangSoanThao] = TimeSpan.FromDays(7),
        [ContractPhase.TraLai] = TimeSpan.FromDays(7),
        [ContractPhase.DangGopY] = TimeSpan.FromDays(7),
        [ContractPhase.DangDamPhan] = TimeSpan.FromDays(7),
        [ContractPhase.DangInKy] = TimeSpan.FromDays(1),
        [ContractPhase.DangKiemTraCCM] = TimeSpan.FromDays(3),
        [ContractPhase.DangTrinhKy] = TimeSpan.FromDays(1),
        [ContractPhase.DangDongDau] = null,
        [ContractPhase.DaPhatHanh] = null,
        [ContractPhase.TuChoi] = null,
        [ContractPhase.DangChon] = null,
    };

    // ===== STANDARD: 9-phase formal workflow =====
    // Per QT-TP-NCC.docx: Thầu phụ / NCC / Tổ đội — full CCM review required.
    // Session 17: Reject = về TraLai (Phase riêng). Drafter từ TraLai gửi lại
    // = entry point thứ 2 (mirror DangSoanThao → DangGopY).
    public static readonly WorkflowPolicy Standard = new(
        Name: "Standard",
        Description: "Quy trình đầy đủ 8 phase — CCM kiểm tra + BOD duyệt. Áp dụng HĐ Thầu phụ / NCC / Giao khoán.",
        Transitions: new Dictionary<(ContractPhase, ContractPhase), string[]>
        {
            [(ContractPhase.DangSoanThao, ContractPhase.DangGopY)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.DangSoanThao, ContractPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.TraLai, ContractPhase.DangGopY)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.TraLai, ContractPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],

            [(ContractPhase.DangGopY, ContractPhase.DangDamPhan)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.DangGopY, ContractPhase.TraLai)] = [AppRoles.ProjectManager, AppRoles.Procurement, AppRoles.CostControl, AppRoles.Finance, AppRoles.Accounting, AppRoles.Equipment],

            [(ContractPhase.DangDamPhan, ContractPhase.DangInKy)] = [AppRoles.Drafter, AppRoles.DeptManager, AppRoles.ProjectManager],

            [(ContractPhase.DangInKy, ContractPhase.DangKiemTraCCM)] = [AppRoles.Drafter, AppRoles.DeptManager, AppRoles.ProjectManager],

            [(ContractPhase.DangKiemTraCCM, ContractPhase.DangTrinhKy)] = [AppRoles.CostControl],
            [(ContractPhase.DangKiemTraCCM, ContractPhase.TraLai)] = [AppRoles.CostControl],

            [(ContractPhase.DangTrinhKy, ContractPhase.DangDongDau)] = [AppRoles.Director, AppRoles.AuthorizedSigner],
            [(ContractPhase.DangTrinhKy, ContractPhase.TraLai)] = [AppRoles.Director, AppRoles.AuthorizedSigner],

            [(ContractPhase.DangDongDau, ContractPhase.DaPhatHanh)] = [AppRoles.HrAdmin],
        },
        PhaseSla: DefaultSla,
        ActivePhases:
        [
            ContractPhase.DangSoanThao, ContractPhase.TraLai, ContractPhase.DangGopY, ContractPhase.DangDamPhan,
            ContractPhase.DangInKy, ContractPhase.DangKiemTraCCM, ContractPhase.DangTrinhKy,
            ContractPhase.DangDongDau, ContractPhase.DaPhatHanh, ContractPhase.TuChoi,
        ]);

    // ===== SKIP-CCM: 7-phase for service / purchase contracts =====
    // Áp dụng HĐ Dịch vụ, Mua bán — không cần CCM review riêng, đi thẳng từ
    // DangInKy → DangTrinhKy (BOD vẫn duyệt).
    public static readonly WorkflowPolicy SkipCcm = new(
        Name: "SkipCcm",
        Description: "Bỏ phase CCM — DangInKy đi thẳng DangTrinhKy. Áp dụng HĐ Dịch vụ / Mua bán / HĐ Nguyên tắc.",
        Transitions: new Dictionary<(ContractPhase, ContractPhase), string[]>
        {
            [(ContractPhase.DangSoanThao, ContractPhase.DangGopY)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.DangSoanThao, ContractPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.TraLai, ContractPhase.DangGopY)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.TraLai, ContractPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],

            [(ContractPhase.DangGopY, ContractPhase.DangDamPhan)] = [AppRoles.Drafter, AppRoles.DeptManager],
            [(ContractPhase.DangGopY, ContractPhase.TraLai)] = [AppRoles.ProjectManager, AppRoles.Procurement, AppRoles.CostControl, AppRoles.Finance, AppRoles.Accounting, AppRoles.Equipment],

            [(ContractPhase.DangDamPhan, ContractPhase.DangInKy)] = [AppRoles.Drafter, AppRoles.DeptManager, AppRoles.ProjectManager],

            // Skip CCM — go straight to BOD
            [(ContractPhase.DangInKy, ContractPhase.DangTrinhKy)] = [AppRoles.Drafter, AppRoles.DeptManager, AppRoles.ProjectManager],

            [(ContractPhase.DangTrinhKy, ContractPhase.DangDongDau)] = [AppRoles.Director, AppRoles.AuthorizedSigner],
            [(ContractPhase.DangTrinhKy, ContractPhase.TraLai)] = [AppRoles.Director, AppRoles.AuthorizedSigner],

            [(ContractPhase.DangDongDau, ContractPhase.DaPhatHanh)] = [AppRoles.HrAdmin],
        },
        PhaseSla: DefaultSla,
        ActivePhases:
        [
            ContractPhase.DangSoanThao, ContractPhase.TraLai, ContractPhase.DangGopY, ContractPhase.DangDamPhan,
            ContractPhase.DangInKy, ContractPhase.DangTrinhKy,
            ContractPhase.DangDongDau, ContractPhase.DaPhatHanh, ContractPhase.TuChoi,
        ]);
}

public static class WorkflowPolicyRegistry
{
    // Available policy names — extend when admin-authored custom policies
    // are added in iteration 2.
    public static readonly string[] AvailablePolicyNames = ["Standard", "SkipCcm"];

    public static WorkflowPolicy ByName(string name) => name switch
    {
        "SkipCcm" => WorkflowPolicies.SkipCcm,
        _ => WorkflowPolicies.Standard,
    };

    // Default mapping per contract type — used when DB override missing.
    // Matches business from QT-TP-NCC.docx.
    public static string DefaultPolicyNameFor(ContractType type) => type switch
    {
        ContractType.HopDongThauPhu or ContractType.HopDongGiaoKhoan or ContractType.HopDongNhaCungCap => "Standard",
        _ => "SkipCcm",
    };

    public static WorkflowPolicy For(ContractType type) => ByName(DefaultPolicyNameFor(type));

    // Instance-level bypass flag overrides the policy — if a contract has
    // BypassProcurementAndCCM=true, always use SkipCcm regardless of type.
    public static WorkflowPolicy ForContract(Contract contract) =>
        contract.BypassProcurementAndCCM ? WorkflowPolicies.SkipCcm : For(contract.Type);

    // DB-aware resolver: prefer assignment, else default. Pass in the
    // assignments dict once per request (cached from DB).
    public static WorkflowPolicy ForContractWithOverrides(
        Contract contract,
        IReadOnlyDictionary<ContractType, string>? assignments)
    {
        if (contract.BypassProcurementAndCCM) return WorkflowPolicies.SkipCcm;
        if (assignments is not null && assignments.TryGetValue(contract.Type, out var policyName))
            return ByName(policyName);
        return For(contract.Type);
    }

    // Build a policy from a persisted WorkflowDefinition (admin-authored).
    // Transitions derived từ ordered steps: prev.Phase → step.Phase,
    // allowed roles = Role-kind approvers' names. Reject-back-to-Drafter +
    // TuChoi paths auto-wired để guard không block common flows.
    //
    // User-kind approvers (iter 2): populate UserTransitions parallel dict —
    // ContractWorkflowService check Role first, fallback User-kind nếu role
    // không match. Cho phép admin gán cụ thể "chỉ user X được duyệt" ngoài
    // role-based.
    public static WorkflowPolicy FromDefinition(WorkflowDefinition def)
    {
        var steps = def.Steps.OrderBy(s => s.Order).ToList();
        var transitions = new Dictionary<(ContractPhase From, ContractPhase To), string[]>();
        var userTransitions = new Dictionary<(ContractPhase From, ContractPhase To), string[]>();
        var sla = new Dictionary<ContractPhase, TimeSpan?>();
        var activePhases = new List<ContractPhase>();

        ContractPhase? prev = null;
        foreach (var s in steps)
        {
            activePhases.Add(s.Phase);
            sla[s.Phase] = s.SlaDays is int d ? TimeSpan.FromDays(d) : null;

            var roles = s.Approvers
                .Where(a => a.Kind == WorkflowApproverKind.Role)
                .Select(a => a.AssignmentValue)
                .Distinct()
                .ToArray();
            // Nếu step không có Role nào nhưng CÓ User-kind, không fallback
            // DeptManager nữa — leave roles empty, guard sẽ check user-level.
            // Chỉ fallback DeptManager nếu step thiếu cả 2 (cấu hình broken).
            var hasUserKind = s.Approvers.Any(a => a.Kind == WorkflowApproverKind.User);
            if (roles.Length == 0 && !hasUserKind) roles = [AppRoles.DeptManager];

            var userIds = s.Approvers
                .Where(a => a.Kind == WorkflowApproverKind.User)
                .Select(a => a.AssignmentValue)
                .Distinct()
                .ToArray();

            if (prev is not null)
            {
                transitions[(prev.Value, s.Phase)] = roles;
                if (userIds.Length > 0) userTransitions[(prev.Value, s.Phase)] = userIds;

                // Mirror: TraLai → s.Phase (Drafter resubmit từ Trả lại = entry point thứ 2)
                if (prev.Value == ContractPhase.DangSoanThao)
                {
                    transitions.TryAdd((ContractPhase.TraLai, s.Phase), roles);
                    if (userIds.Length > 0)
                        userTransitions.TryAdd((ContractPhase.TraLai, s.Phase), userIds);
                }

                // Reject path → TraLai (Phase riêng, không revert DangSoanThao)
                if (prev.Value != ContractPhase.DangSoanThao && s.Phase != ContractPhase.DangSoanThao)
                {
                    transitions.TryAdd((s.Phase, ContractPhase.TraLai), roles);
                    if (userIds.Length > 0)
                        userTransitions.TryAdd((s.Phase, ContractPhase.TraLai), userIds);
                }
            }
            prev = s.Phase;
        }
        // First step có thể reject to TuChoi (cả Nháp + Trả lại)
        if (steps.Count > 0)
        {
            transitions.TryAdd((steps[0].Phase, ContractPhase.TuChoi),
                [AppRoles.Drafter, AppRoles.DeptManager]);
            transitions.TryAdd((ContractPhase.TraLai, ContractPhase.TuChoi),
                [AppRoles.Drafter, AppRoles.DeptManager]);
        }

        if (!activePhases.Contains(ContractPhase.TuChoi)) activePhases.Add(ContractPhase.TuChoi);
        if (!activePhases.Contains(ContractPhase.TraLai)) activePhases.Add(ContractPhase.TraLai);

        return new WorkflowPolicy(
            Name: $"{def.Code}-v{def.Version:D2}",
            Description: def.Description ?? def.Name,
            Transitions: transitions,
            PhaseSla: sla,
            ActivePhases: activePhases,
            UserTransitions: userTransitions.Count > 0 ? userTransitions : null);
    }
}
