using ERPCore.Domain;
using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Dtos.Common;
using ERPCore.Dtos.Procurement;
using ERPCore.Infra.Auth;
using ERPCore.Infra.UoW;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using ERPCore.System.Errors;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services;

public sealed class RequisitionService : IRequisitionService
{
    private readonly IRepository<Requisition> _requisitions;
    private readonly IRepository<Item> _items;
    private readonly INumberSequenceService _numbers;
    private readonly ICurrentUser _currentUser;
    private readonly IUnitOfWork _uow;

    public RequisitionService(
        IRepository<Requisition> requisitions, IRepository<Item> items,
        INumberSequenceService numbers, ICurrentUser currentUser, IUnitOfWork uow)
    {
        _requisitions = requisitions;
        _items = items;
        _numbers = numbers;
        _currentUser = currentUser;
        _uow = uow;
    }

    public async Task<PagedResponse<RequisitionSummaryDto>> ListAsync(
        PageQuery query, RequisitionStatus? status, CancellationToken ct = default)
    {
        var q = _requisitions.Query().AsNoTracking();
        if (!string.IsNullOrWhiteSpace(query.Q))
        {
            var term = query.Q.Trim();
            q = q.Where(r => EF.Functions.ILike(r.DocNo, $"%{term}%"));
        }
        if (status is not null) q = q.Where(r => r.Status == status);

        var total = await q.CountAsync(ct);
        var rows = await q.OrderByDescending(r => r.RequisitionId)
            .Skip(query.Skip).Take(query.PageSize)
            .Select(r => new RequisitionSummaryDto(
                r.RequisitionId, r.DocNo, r.Status, r.RequestedBy, r.CreatedAt, r.Lines.Count))
            .ToListAsync(ct);

        return PagedResponse<RequisitionSummaryDto>.Create(rows, query.Page, query.PageSize, total);
    }

    public async Task<RequisitionDto?> GetAsync(int requisitionId, CancellationToken ct = default)
    {
        var req = await _requisitions.Query().AsNoTracking()
            .Include(r => r.Lines)
            .FirstOrDefaultAsync(r => r.RequisitionId == requisitionId, ct);
        return req is null ? null : Map(req);
    }

    public async Task<RequisitionDto> CreateAsync(CreateRequisitionRequest request, CancellationToken ct = default)
    {
        await EnsureItemsExistAsync(request.Lines.Select(l => l.ItemId), ct);
        var actor = _currentUser.AuditUserId;

        var req = await _uow.ExecuteInTransactionAsync(async token =>
        {
            var docNo = await _numbers.NextAsync(DocumentTypes.Requisition, token);
            var entity = new Requisition
            {
                DocNo = docNo,
                RequestedBy = actor,
                Status = RequisitionStatus.Draft,
                CreatedAt = DateTime.UtcNow,
                Lines = request.Lines.Select(l => new RequisitionLine
                {
                    ItemId = l.ItemId,
                    Qty = l.Qty,
                    RequiredBy = l.RequiredBy
                }).ToList()
            };
            await _requisitions.AddAsync(entity, token);
            return entity;
        }, ct);

        return Map(req);
    }

    public async Task<RequisitionDto> SubmitAsync(int requisitionId, CancellationToken ct = default)
    {
        var req = await _requisitions.Query()
            .Include(r => r.Lines)
            .FirstOrDefaultAsync(r => r.RequisitionId == requisitionId, ct)
            ?? throw new NotFoundException($"Requisition {requisitionId} was not found.");

        if (req.Status != RequisitionStatus.Submitted)
        {
            req.Status = RequisitionStatus.Submitted;
            await _uow.SaveChangesAsync(ct);
        }

        return Map(req);
    }

    private async Task EnsureItemsExistAsync(IEnumerable<int> itemIds, CancellationToken ct)
    {
        var ids = itemIds.Distinct().ToList();
        var found = await _items.Query().AsNoTracking()
            .Where(i => ids.Contains(i.ItemId)).Select(i => i.ItemId).ToListAsync(ct);
        var missing = ids.Except(found).ToList();
        if (missing.Count > 0)
            throw new DomainException(ErrorCodes.Validation, $"Item(s) not found: {string.Join(", ", missing)}.", 422);
    }

    private static RequisitionDto Map(Requisition r) => new(
        r.RequisitionId, r.DocNo, r.Status, r.RequestedBy, r.CreatedAt,
        r.Lines.OrderBy(l => l.ReqLineId)
            .Select(l => new RequisitionLineDto(l.ReqLineId, l.ItemId, l.Qty, l.RequiredBy))
            .ToList());
}
