using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Contracts.Services;
using SolutionErp.Application.Notifications;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Notifications;

namespace SolutionErp.Infrastructure.HostedServices;

// Background service chạy mỗi 15 phút, auto-approve HĐ quá SLA.
// Xem docs/flows/sla-expiry-flow.md cho spec full.
// Phase 3 iteration 2 MVP — chưa có notification, chỉ log + ContractApproval với Decision=AutoApprove.
public class SlaExpiryJob : BackgroundService
{
    private readonly IServiceProvider _sp;
    private readonly ILogger<SlaExpiryJob> _logger;
    private static readonly TimeSpan Interval = TimeSpan.FromMinutes(15);

    // Map phase → phase tiếp (happy path). Nếu null → không auto-approve (final/TuChoi).
    private static readonly Dictionary<ContractPhase, ContractPhase?> NextPhase = new()
    {
        [ContractPhase.DangSoanThao] = ContractPhase.DangGopY,
        [ContractPhase.DangGopY] = ContractPhase.DangDamPhan,
        [ContractPhase.DangDamPhan] = ContractPhase.DangInKy,
        [ContractPhase.DangInKy] = ContractPhase.DangKiemTraCCM,
        [ContractPhase.DangKiemTraCCM] = ContractPhase.DangTrinhKy,
        [ContractPhase.DangTrinhKy] = ContractPhase.DangDongDau,
        [ContractPhase.DangDongDau] = ContractPhase.DaPhatHanh,
    };

    public SlaExpiryJob(IServiceProvider sp, ILogger<SlaExpiryJob> logger)
    {
        _sp = sp;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Wait 30s sau khi app start — tránh race với DbInitializer migrate
        await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await ProcessAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "SlaExpiryJob iteration failed");
            }
            await Task.Delay(Interval, stoppingToken);
        }
    }

    private async Task ProcessAsync(CancellationToken ct)
    {
        await using var scope = _sp.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<IApplicationDbContext>();
        var workflow = scope.ServiceProvider.GetRequiredService<IContractWorkflowService>();
        var dateTime = scope.ServiceProvider.GetRequiredService<IDateTime>();
        var notifications = scope.ServiceProvider.GetRequiredService<INotificationService>();

        var now = dateTime.UtcNow;

        // Step 1: Warning 80% SLA — gửi notification 1 lần (track SlaWarningSent
        // flag). Tính: nếu (now - createdLastTransition) >= 80% (SlaDeadline -
        // createdLastTransition). Approximation đơn giản: warning khi SlaDeadline
        // - now <= 20% × default SLA. Chính xác hơn cần track phaseStartAt, để
        // iter sau.
        await ProcessWarningsAsync(db, notifications, now, ct);

        var expired = await db.Contracts
            .Where(c => c.SlaDeadline != null && c.SlaDeadline < now)
            .Where(c => c.Phase != ContractPhase.DaPhatHanh && c.Phase != ContractPhase.TuChoi)
            .ToListAsync(ct);

        if (expired.Count == 0)
        {
            _logger.LogDebug("SlaExpiryJob: no expired contracts");
            return;
        }

        _logger.LogInformation("SlaExpiryJob: {Count} expired contracts", expired.Count);

        foreach (var contract in expired)
        {
            if (!NextPhase.TryGetValue(contract.Phase, out var next) || next is null)
            {
                _logger.LogWarning("SlaExpiryJob: phase {Phase} không có next, skip contract {Id}", contract.Phase, contract.Id);
                continue;
            }

            try
            {
                var hoursOver = (int)(now - contract.SlaDeadline!.Value).TotalHours;
                await workflow.TransitionAsync(
                    contract,
                    next.Value,
                    actorUserId: null,                         // system
                    actorRoles: new List<string>(),            // system bypass check trong workflow
                    decision: ApprovalDecision.AutoApprove,
                    comment: $"[AUTO] Hết SLA phase {contract.Phase} ({hoursOver}h quá hạn). Tự động chuyển sang {next.Value}.",
                    ct: ct);
                _logger.LogInformation("SlaExpiryJob: auto-approved contract {Id} {From} → {To}",
                    contract.Id, contract.Phase, next.Value);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "SlaExpiryJob: failed auto-approve {Id}", contract.Id);
            }
        }
    }

    // Warning notification khi HĐ còn ≤20% SLA của phase hiện tại. Track
    // SlaWarningSent flag để chỉ gửi 1 lần per phase (reset khi transition).
    // Drafter + role giữ phase đều nhận notification (drafter để theo dõi,
    // role để remind action).
    private async Task ProcessWarningsAsync(
        IApplicationDbContext db, INotificationService notifications,
        DateTime now, CancellationToken ct)
    {
        // Default SLA durations (sync với WorkflowPolicies.DefaultSla)
        var defaultSla = new Dictionary<ContractPhase, TimeSpan>
        {
            [ContractPhase.DangSoanThao] = 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),
        };

        // Pull tất cả HĐ chưa warning + sắp quá hạn
        var candidates = await db.Contracts
            .Where(c => !c.SlaWarningSent
                && c.SlaDeadline != null && c.SlaDeadline > now
                && c.Phase != ContractPhase.DaPhatHanh
                && c.Phase != ContractPhase.TuChoi
                && c.Phase != ContractPhase.DangDongDau)
            .ToListAsync(ct);

        if (candidates.Count == 0) return;

        int warned = 0;
        foreach (var c in candidates)
        {
            if (!defaultSla.TryGetValue(c.Phase, out var sla)) continue;
            var threshold = TimeSpan.FromTicks((long)(sla.Ticks * 0.2));
            var remaining = c.SlaDeadline!.Value - now;
            if (remaining > threshold) continue;        // còn nhiều SLA → skip

            // ≤ 20% SLA còn lại → gửi warning
            if (c.DrafterUserId is Guid drafterId)
            {
                var hoursLeft = Math.Max(1, (int)remaining.TotalHours);
                await notifications.NotifyAsync(
                    drafterId,
                    NotificationType.SlaWarning,
                    title: $"⚠ HĐ {c.MaHopDong ?? c.TenHopDong} sắp quá hạn ({hoursLeft}h)",
                    description: $"Phase {c.Phase} còn ~{hoursLeft}h trước khi auto-approve.",
                    href: $"/contracts/{c.Id}",
                    refId: c.Id,
                    ct: ct);
            }
            c.SlaWarningSent = true;
            warned++;
        }

        if (warned > 0)
        {
            await db.SaveChangesAsync(ct);
            _logger.LogInformation("SlaExpiryJob: {Count} warnings dispatched (≤20% SLA).", warned);
        }
    }
}
