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;

// Thin orchestrator — all phase/role/SLA rules live in WorkflowPolicy (Domain).
// This class is responsible only for *applying* transitions: DB writes, code
// generation at DangDongDau, SLA deadline computation, notification dispatch.
public class ContractWorkflowService(
    IApplicationDbContext db,
    IContractCodeGenerator codeGenerator,
    IDateTime dateTime,
    INotificationService notifications,
    IChangelogService changelog,
    UserManager<User> userManager) : IContractWorkflowService
{
    // Expose per-policy SLA via the contract — accepts optional contract so the
    // caller (CreateContractCommand) can ask for a specific type's SLA even
    // before the contract exists.
    public TimeSpan? GetPhaseSla(ContractPhase phase) =>
        WorkflowPolicies.Standard.PhaseSla.GetValueOrDefault(phase);

    public async Task TransitionAsync(
        Contract contract,
        ContractPhase targetPhase,
        Guid? actorUserId,
        IReadOnlyList<string> actorRoles,
        ApprovalDecision decision,
        string? comment,
        CancellationToken ct = default)
    {
        if (contract.Phase == targetPhase)
            throw new ConflictException("HĐ đã ở phase đích.");

        // ===== Smart reject + resume (Phase 9 — Migration 16) =====
        // Reject: override target = DangSoanThao + lưu phase gốc → Drafter sửa.
        // Resume sau reject: Drafter trình từ DangSoanThao + RejectedFromPhase
        // != null → jump straight tới phase đã reject, bypass phase trung gian.
        var fromPhase = contract.Phase;
        var isResumingAfterReject = decision == ApprovalDecision.Approve
            && fromPhase == ContractPhase.DangSoanThao
            && contract.RejectedFromPhase != null;

        if (decision == ApprovalDecision.Reject)
        {
            contract.RejectedFromPhase = fromPhase;
            targetPhase = ContractPhase.DangSoanThao;
        }
        else if (isResumingAfterReject)
        {
            targetPhase = contract.RejectedFromPhase!.Value;
            contract.RejectedFromPhase = null;
        }

        // Resolve the workflow: prefer the pinned WorkflowDefinition (new
        // versioned system), else fall back to the static/override registry
        // (legacy path for contracts created before versioning rolled out).
        WorkflowPolicy policy;
        if (contract.WorkflowDefinitionId is Guid wfId)
        {
            var def = await db.WorkflowDefinitions.AsNoTracking()
                .Include(d => d.Steps.OrderBy(s => s.Order))
                    .ThenInclude(s => s.Approvers)
                .FirstOrDefaultAsync(d => d.Id == wfId, ct);
            policy = def is not null
                ? WorkflowPolicyRegistry.FromDefinition(def)
                : WorkflowPolicyRegistry.ForContract(contract);
        }
        else
        {
            var overrides = await db.WorkflowTypeAssignments.AsNoTracking()
                .ToDictionaryAsync(a => a.ContractType, a => a.PolicyName, ct);
            policy = WorkflowPolicyRegistry.ForContractWithOverrides(contract, overrides);
        }
        var isAdmin = actorRoles.Contains(AppRoles.Admin);
        var isSystem = actorUserId is null && decision == ApprovalDecision.AutoApprove;

        // Policy guard — bypass cho resume (Drafter có quyền trình lại sau khi
        // sửa, không cần policy check vì target đã pinned bởi RejectedFromPhase).
        if (!isAdmin && !isSystem && !isResumingAfterReject)
        {
            if (!policy.Transitions.TryGetValue((fromPhase, targetPhase), out var allowedRoles))
                throw new ForbiddenException(
                    $"Policy '{policy.Name}' không cho phép {fromPhase} → {targetPhase}. " +
                    $"Kiểm tra ContractType hoặc BypassProcurementAndCCM.");

            // Sử dụng IsTransitionAllowed — check Role + User-kind fallback.
            // User-kind chỉ áp dụng khi WorkflowDefinition pinned có
            // WorkflowStepApprover Kind=User cho step này.
            if (!policy.IsTransitionAllowed(fromPhase, targetPhase, actorRoles, actorUserId))
            {
                var userExtra = policy.UserTransitions is not null
                    && policy.UserTransitions.TryGetValue((fromPhase, targetPhase), out var userIds)
                    && userIds.Length > 0
                    ? $" hoặc {userIds.Length} user explicit"
                    : "";
                throw new ForbiddenException(
                    $"Role ({string.Join(",", actorRoles)}) không đủ quyền chuyển {fromPhase} → {targetPhase}. " +
                    $"Policy '{policy.Name}' yêu cầu: {string.Join(",", allowedRoles)}{userExtra}.");
            }
        }

        // ===== 2-stage department approval (Phase 9 — Migration 16) =====
        // Mirror PE workflow service. NV thuộc dept review → BLOCK transition
        // cho đến khi TPB cùng dept confirm. CanBypassReview cho NV → đẩy thẳng
        // Confirm (skip Review). Skip với reject + resume + admin + system.
        if (decision == ApprovalDecision.Approve
            && targetPhase != ContractPhase.DangSoanThao
            && targetPhase != ContractPhase.TuChoi
            && !isResumingAfterReject
            && !isAdmin && !isSystem
            && actorUserId is Guid actorUid)
        {
            var actor = await userManager.FindByIdAsync(actorUid.ToString());
            if (actor?.DepartmentId is Guid deptId)
            {
                var isManager = actorRoles.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");

                // Upsert: 1 row mỗi (ContractId, phase, dept, stage). UNIQUE index enforce.
                var existing = await db.ContractDepartmentApprovals
                    .FirstOrDefaultAsync(a =>
                        a.ContractId == contract.Id
                        && a.PhaseAtApproval == (int)fromPhase
                        && a.DepartmentId == deptId
                        && a.Stage == stage, ct);
                if (existing is null)
                {
                    db.ContractDepartmentApprovals.Add(new ContractDepartmentApproval
                    {
                        ContractId = contract.Id,
                        PhaseAtApproval = (int)fromPhase,
                        DepartmentId = deptId,
                        Stage = stage,
                        ApproverUserId = actorUid,
                        ApproverRoleSnapshot = roleSnapshot,
                        Comment = comment,
                        ApprovedAt = dateTime.UtcNow,
                        IsBypassed = isBypassed,
                    });
                }
                else
                {
                    existing.ApproverUserId = actorUid;
                    existing.ApproverRoleSnapshot = roleSnapshot;
                    existing.Comment = comment;
                    existing.ApprovedAt = dateTime.UtcNow;
                    existing.IsBypassed = isBypassed;
                }

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

                if (!hasConfirm)
                {
                    // NV review xong, chưa có TPB confirm → BLOCK phase transition.
                    db.ContractApprovals.Add(new ContractApproval
                    {
                        ContractId = contract.Id,
                        FromPhase = fromPhase,
                        ToPhase = fromPhase,    // không đổi phase
                        ApproverUserId = actorUid,
                        Decision = ApprovalDecision.Approve,
                        Comment = $"[Review NV] {comment ?? ""}",
                        ApprovedAt = dateTime.UtcNow,
                    });

                    string? reviewerName = (actor.FullName ?? actor.Email);
                    db.ContractChangelogs.Add(new ContractChangelog
                    {
                        ContractId = contract.Id,
                        EntityType = ChangelogEntityType.Workflow,
                        Action = ChangelogAction.Transition,
                        PhaseAtChange = fromPhase,
                        UserId = actorUid,
                        UserName = reviewerName ?? "Hệ thống",
                        Summary = $"{reviewerName} (NV) đã review phase {fromPhase}, chờ TPB confirm",
                        ContextNote = comment,
                    });

                    // Notify TPB cùng dept để confirm. 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: $"HĐ {contract.MaHopDong ?? contract.TenHopDong ?? ""} chờ TPB confirm",
                                description: $"NV {reviewerName} đã review phase {fromPhase}. Vui lòng vào confirm để workflow tiếp tục.",
                                href: $"/contracts/{contract.Id}",
                                refId: contract.Id,
                                ct: ct);
                        }
                    }
                    catch { /* notification fail non-critical */ }

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

        // Defensive — gen mã HĐ nếu chưa có khi chuyển sang DangDongDau.
        // Nominal flow (sau user feedback): mã đã gen sẵn từ CreateContract → skip.
        // Fallback chỉ trigger cho HĐ legacy chưa qua backfill, hoặc HĐ tạo bằng
        // path khác (vd seed/import) chưa set MaHopDong.
        if (targetPhase == ContractPhase.DangDongDau && 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.SlaWarningSent = false;
        contract.Phase = targetPhase;

        var sla = policy.PhaseSla.GetValueOrDefault(targetPhase);
        contract.SlaDeadline = sla is null ? null : dateTime.UtcNow.Add(sla.Value);

        db.ContractApprovals.Add(new ContractApproval
        {
            ContractId = contract.Id,
            FromPhase = fromPhase,
            ToPhase = targetPhase,
            ApproverUserId = actorUserId,
            Decision = decision,
            Comment = comment,
            ApprovedAt = dateTime.UtcNow,
        });

        // Log workflow transition vào unified Changelog (cho user xem trên tab Lịch sử)
        await changelog.LogWorkflowTransitionAsync(contract.Id, fromPhase, targetPhase, comment);

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

        await db.SaveChangesAsync(ct);
    }
}
