using Lada.Framework.Data.Domains.Orders;
using Lada.Framework.Data.Domains.Finance.Wallets;
using Lada.Framework.Data.Domains.Rewards;
using Lada.Framework.Data.Enums;
using static Lada.Framework.Data.Enums.OrderFinEnum;
using Lada.Framework.Data.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Ladaer.BackEnd.Infrastructures.Jobs
{
    /// <summary>
    /// Hangfire job tự động phát hiện vi phạm nộp tiền trễ và sinh phiếu phạt.
    /// Chạy định kỳ (VD: mỗi 30 phút).
    /// </summary>
    public class AutoPenaltyJob
    {
        private readonly IRepository<AutoPenaltyConfig> _configRepo;
        private readonly IRepository<AutoPenaltyLevel> _levelRepo;
        private readonly IRepository<UserCashViolation> _violationRepo;
        private readonly IRepository<RewardPenaltyTicket> _ticketRepo;
        private readonly IRepository<OrderFin> _orderFinRepo;
        private readonly IRepository<Transaction> _transactionRepo;
        private readonly ILogger<AutoPenaltyJob> _logger;

        public AutoPenaltyJob(
            IRepository<AutoPenaltyConfig> configRepo,
            IRepository<AutoPenaltyLevel> levelRepo,
            IRepository<UserCashViolation> violationRepo,
            IRepository<RewardPenaltyTicket> ticketRepo,
            IRepository<OrderFin> orderFinRepo,
            IRepository<Transaction> transactionRepo,
            ILogger<AutoPenaltyJob> logger)
        {
            _configRepo = configRepo;
            _levelRepo = levelRepo;
            _violationRepo = violationRepo;
            _ticketRepo = ticketRepo;
            _orderFinRepo = orderFinRepo;
            _transactionRepo = transactionRepo;
            _logger = logger;
        }

        /// <summary>
        /// Execute job - phát hiện vi phạm nộp tiền trễ ngày hôm qua
        /// </summary>
        public async Task Execute()
        {
            _logger.LogInformation("AutoPenaltyJob: Bắt đầu chạy...");

            // 1. Lấy cấu hình hiện hành
            var config = await _configRepo.Query()
                .Include(c => c.Levels)
                .Where(c => c.EffectiveTo == null)
                .OrderByDescending(c => c.EffectiveFrom)
                .FirstOrDefaultAsync();

            if (config == null || !config.IsEnabled || config.IsPaused)
            {
                _logger.LogInformation("AutoPenaltyJob: Skipped (disabled/paused hoặc chưa có config)");
                return;
            }

            // 2. Xác định BusinessDate = Hôm qua, DeadlineAt = Hôm nay + CutoffTime
            var businessDate = DateTime.Today.AddDays(-1);
            var deadlineAt = new DateTimeOffset(
                DateTime.Today.Add(config.CutoffTimeNextDay),
                DateTimeOffset.Now.Offset);

            // Chưa tới hạn chót thì chưa xử lý
            if (DateTimeOffset.Now < deadlineAt)
            {
                _logger.LogInformation("AutoPenaltyJob: Chưa tới deadline ({DeadlineAt}), skip", deadlineAt);
                return;
            }

            _logger.LogInformation("AutoPenaltyJob: BusinessDate={BusinessDate}, Deadline={DeadlineAt}",
                businessDate, deadlineAt);

            // 3. Lấy DS User đang cầm tiền ngày hôm qua
            var cashierDebts = await GetCashierDebts(businessDate);

            if (!cashierDebts.Any())
            {
                _logger.LogInformation("AutoPenaltyJob: Không có user nào cầm tiền ngày {BusinessDate}", businessDate);
                return;
            }

            _logger.LogInformation("AutoPenaltyJob: Tìm thấy {Count} user cầm tiền", cashierDebts.Count);

            // 4. Xử lý từng user
            foreach (var debt in cashierDebts)
            {
                try
                {
                    await ProcessUserDebt(debt, businessDate, deadlineAt, config);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "AutoPenaltyJob: Lỗi xử lý user {UserId}", debt.UserId);
                }
            }

            _logger.LogInformation("AutoPenaltyJob: Hoàn thành");
        }

        private async Task ProcessUserDebt(CashierDebt debt, DateTime businessDate,
            DateTimeOffset deadlineAt, AutoPenaltyConfig config)
        {
            // 4a. Chống trùng
            var existingViolation = await _violationRepo.Query()
                .AnyAsync(v => v.UserId == debt.UserId && v.BusinessDate == businessDate);

            if (existingViolation)
            {
                _logger.LogDebug("AutoPenaltyJob: User {UserId} / {BusinessDate} đã có violation, skip",
                    debt.UserId, businessDate);
                return;
            }

            // 4b. Tính AmountPaid trước deadline
            var amountPaid = await CalculateAmountPaid(debt.UserId, deadlineAt);

            // 4c. Kiểm tra đã nộp đủ chưa
            if (amountPaid >= debt.AmountDue)
            {
                _logger.LogDebug("AutoPenaltyJob: User {UserId} đã nộp đủ ({AmountPaid} >= {AmountDue})",
                    debt.UserId, amountPaid, debt.AmountDue);
                return;
            }

            var outstandingAmount = debt.AmountDue - amountPaid;

            // 4d. Tính ViolationCount trong chu kỳ (bỏ qua Forgiven)
            var cycleStart = GetCycleStart(config.ResetPolicy);
            var violationCountInCycle = await _violationRepo.Query()
                .CountAsync(v => v.UserId == debt.UserId
                    && v.BusinessDate >= cycleStart.DateTime
                    && v.Status != ViolationStatus.Forgiven);

            var violationCount = violationCountInCycle + 1;

            // 4e. Tạo UserCashViolation
            var violation = new UserCashViolation
            {
                UserId = debt.UserId,
                BusinessDate = businessDate,
                DeadlineAt = deadlineAt,
                AmountDue = debt.AmountDue,
                AmountPaid = amountPaid,
                OutstandingAmount = outstandingAmount,
                ViolationCountAtCreation = violationCount,
                ConfigIdApplied = config.Id,
                Status = ViolationStatus.Active,
                CreatedTime = DateTimeOffset.Now
            };

            try
            {
                await _violationRepo.InsertAsync(violation);
            }
            catch (DbUpdateException)
            {
                // Duplicate key (Unique Index) → idempotent, skip
                _logger.LogWarning("AutoPenaltyJob: Duplicate violation cho User {UserId} / {BusinessDate}, skip",
                    debt.UserId, businessDate);
                return;
            }

            _logger.LogInformation("AutoPenaltyJob: Tạo violation #{ViolationId} cho User {UserId}, lần thứ {Count}",
                violation.Id, debt.UserId, violationCount);

            // 4f. Tra bảng AutoPenaltyLevel
            var level = config.Levels?
                .Where(l => l.FromCount <= violationCount
                    && (l.ToCount == null || l.ToCount >= violationCount))
                .FirstOrDefault();

            if (level == null)
            {
                _logger.LogWarning("AutoPenaltyJob: Không tìm thấy level cho violationCount={Count}", violationCount);
                return;
            }

            // 4g. Sinh phiếu RewardPenaltyTicket
            var ticket = new RewardPenaltyTicket
            {
                Code = $"RP-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..6].ToUpper()}",
                UserId = debt.UserId,
                TicketType = TicketType.Penalty,
                Source = TicketSource.Automated,
                Amount = level.PenaltyAmount,
                Reason = level.ActionType == PenaltyActionType.Remind
                    ? $"Nhắc nhở: Nộp tiền trễ ngày {businessDate:dd/MM/yyyy} (lần {violationCount})"
                    : $"Phạt nộp tiền trễ ngày {businessDate:dd/MM/yyyy} (lần {violationCount}), còn thiếu {outstandingAmount:N0}đ",
                Status = level.ActionType == PenaltyActionType.Remind
                    ? TicketStatus.Approved
                    : TicketStatus.PendingApproval,
                ViolationId = violation.Id,
                BusinessDate = businessDate,
                CreatedTime = DateTimeOffset.Now
            };

            await _ticketRepo.InsertAsync(ticket);

            // 4h. Update violation status
            violation.Status = ViolationStatus.TicketCreated;
            await _violationRepo.UpdateAsync(violation);

            _logger.LogInformation("AutoPenaltyJob: Tạo ticket #{TicketId} ({ActionType}, {Amount}đ) cho User {UserId}",
                ticket.Id, level.ActionType, level.PenaltyAmount, debt.UserId);
        }

        /// <summary>
        /// Lấy DS người đang cầm tiền theo ngày từ OrderFin
        /// </summary>
        private async Task<List<CashierDebt>> GetCashierDebts(DateTime businessDate)
        {
            var orderFins = await _orderFinRepo.Query()
                .Where(of => !of.IsDeleted)
                .ToListAsync();

            var debts = new Dictionary<int, decimal>();

            foreach (var of in orderFins)
            {
                // COD
                if (of.CodCashierId > 0
                    && of.CodCollectedTime.HasValue
                    && of.CodCollectedTime.Value.Date == businessDate
                    && of.CodStage != OrderCodStage.NotCollected
                    && of.CodStage != OrderCodStage.Head)
                {
                    AddDebt(debts, of.CodCashierId, of.CollectAmount);
                }

                // SourceFee
                if (of.SourceFeeCashierId > 0
                    && of.SourceFeeCollectedTime.HasValue
                    && of.SourceFeeCollectedTime.Value.Date == businessDate
                    && of.SourceFeeStage != OrderFeeStage.NotCollected
                    && of.SourceFeeStage != OrderFeeStage.Head)
                {
                    AddDebt(debts, of.SourceFeeCashierId, of.SourceFee);
                }

                // DestFee
                if (of.DestFeeCashierId > 0
                    && of.DestFeeCollectedTime.HasValue
                    && of.DestFeeCollectedTime.Value.Date == businessDate
                    && of.DestFeeStage != OrderFeeStage.NotCollected
                    && of.DestFeeStage != OrderFeeStage.Head)
                {
                    AddDebt(debts, of.DestFeeCashierId, of.DestFee);
                }
            }

            return debts
                .Where(d => d.Value > 0)
                .Select(d => new CashierDebt { UserId = d.Key, AmountDue = d.Value })
                .ToList();
        }

        private static void AddDebt(Dictionary<int, decimal> debts, int cashierId, decimal amount)
        {
            if (!debts.ContainsKey(cashierId))
                debts[cashierId] = 0;
            debts[cashierId] += amount;
        }

        /// <summary>
        /// Tính tổng tiền đã nộp trước deadline từ Transaction
        /// </summary>
        private async Task<decimal> CalculateAmountPaid(int userId, DateTimeOffset deadlineAt)
        {
            return await _transactionRepo.Query()
                .Where(t => !t.IsDeleted
                    && t.Wallet != null
                    && t.Wallet.UserId == userId
                    && t.TransactionType == TransactionType.CollectFromDriver
                    && t.CreatedTime.HasValue
                    && t.CreatedTime <= deadlineAt)
                .SumAsync(t => t.Amount);
        }

        private static DateTimeOffset GetCycleStart(ResetPolicy policy)
        {
            var now = DateTimeOffset.Now;
            return policy switch
            {
                ResetPolicy.Monthly => new DateTimeOffset(now.Year, now.Month, 1, 0, 0, 0, now.Offset),
                ResetPolicy.Quarterly => new DateTimeOffset(now.Year, ((now.Month - 1) / 3) * 3 + 1, 1, 0, 0, 0, now.Offset),
                ResetPolicy.Lifetime => DateTimeOffset.MinValue,
                _ => new DateTimeOffset(now.Year, now.Month, 1, 0, 0, 0, now.Offset)
            };
        }

        private class CashierDebt
        {
            public int UserId { get; set; }
            public decimal AmountDue { get; set; }
        }
    }
}
