using ERPCore.Domain.Entities;
using ERPCore.Dtos.Common;
using ERPCore.Dtos.Warehouses;
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 WarehouseService : IWarehouseService
{
    private readonly IRepository<Warehouse> _warehouses;
    private readonly IRepository<Bin> _bins;
    private readonly IUnitOfWork _uow;

    public WarehouseService(IRepository<Warehouse> warehouses, IRepository<Bin> bins, IUnitOfWork uow)
    {
        _warehouses = warehouses;
        _bins = bins;
        _uow = uow;
    }

    public async Task<PagedResponse<WarehouseDto>> ListAsync(PageQuery query, CancellationToken ct = default)
    {
        var q = _warehouses.Query().AsNoTracking();
        if (!string.IsNullOrWhiteSpace(query.Q))
        {
            var term = query.Q.Trim();
            q = q.Where(w => EF.Functions.ILike(w.Code, $"%{term}%") || EF.Functions.ILike(w.Name, $"%{term}%"));
        }

        var total = await q.CountAsync(ct);
        var rows = await q.OrderBy(w => w.Code)
            .Skip(query.Skip).Take(query.PageSize)
            .Select(w => new WarehouseDto(w.WarehouseId, w.Code, w.Name))
            .ToListAsync(ct);

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

    public async Task<WarehouseDto?> GetAsync(int warehouseId, CancellationToken ct = default)
    {
        var w = await _warehouses.Query().AsNoTracking()
            .FirstOrDefaultAsync(x => x.WarehouseId == warehouseId, ct);
        return w is null ? null : new WarehouseDto(w.WarehouseId, w.Code, w.Name);
    }

    public async Task<WarehouseDto> CreateAsync(CreateWarehouseRequest request, CancellationToken ct = default)
    {
        var code = request.Code.Trim();
        if (await _warehouses.Query().AnyAsync(w => w.Code == code, ct))
            throw new ConflictException($"A warehouse with code '{code}' already exists.");

        var warehouse = new Warehouse { Code = code, Name = request.Name.Trim() };
        await _warehouses.AddAsync(warehouse, ct);
        await _uow.SaveChangesAsync(ct);

        return new WarehouseDto(warehouse.WarehouseId, warehouse.Code, warehouse.Name);
    }

    public async Task<IReadOnlyList<BinDto>> ListBinsAsync(int warehouseId, CancellationToken ct = default)
    {
        await EnsureWarehouseExistsAsync(warehouseId, ct);

        return await _bins.Query().AsNoTracking()
            .Where(b => b.WarehouseId == warehouseId)
            .OrderBy(b => b.Code)
            .Select(b => new BinDto(b.BinId, b.WarehouseId, b.Code, b.BinType))
            .ToListAsync(ct);
    }

    public async Task<BinDto> CreateBinAsync(int warehouseId, CreateBinRequest request, CancellationToken ct = default)
    {
        await EnsureWarehouseExistsAsync(warehouseId, ct);

        var code = request.Code.Trim();
        if (await _bins.Query().AnyAsync(b => b.WarehouseId == warehouseId && b.Code == code, ct))
            throw new ConflictException($"Bin '{code}' already exists in warehouse {warehouseId}.");

        var bin = new Bin { WarehouseId = warehouseId, Code = code, BinType = request.BinType };
        await _bins.AddAsync(bin, ct);
        await _uow.SaveChangesAsync(ct);

        return new BinDto(bin.BinId, bin.WarehouseId, bin.Code, bin.BinType);
    }

    private async Task EnsureWarehouseExistsAsync(int warehouseId, CancellationToken ct)
    {
        if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == warehouseId, ct))
            throw new NotFoundException($"Warehouse {warehouseId} was not found.");
    }
}
