using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using ERPCore.System.Errors;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services;

public sealed class SalesDomainService : ISalesDomainService
{
    private readonly IRepository<Customer> _customers;
    private readonly IRepository<Warehouse> _warehouses;
    private readonly IRepository<User> _users;
    private readonly IRepository<Item> _items;
    private readonly ISalesPricingService _pricing;

    public SalesDomainService(
        IRepository<Customer> customers,
        IRepository<Warehouse> warehouses,
        IRepository<User> users,
        IRepository<Item> items,
        ISalesPricingService pricing)
    {
        _customers = customers;
        _warehouses = warehouses;
        _users = users;
        _items = items;
        _pricing = pricing;
    }

    public async Task ValidateSalesHeaderAsync(int customerId, int warehouseId, int? cashierUserId, bool requireCashierUser, CancellationToken ct = default)
    {
        if (!await _customers.Query().AnyAsync(x => x.CustomerId == customerId, ct))
            throw new NotFoundException($"Customer {customerId} was not found.");
        if (!await _warehouses.Query().AnyAsync(x => x.WarehouseId == warehouseId, ct))
            throw new NotFoundException($"Warehouse {warehouseId} was not found.");
        if (requireCashierUser)
        {
            if (cashierUserId is null)
                throw new DomainException(ErrorCodes.Validation, "Cashier user is required.", 422);
            if (!await _users.Query().AnyAsync(x => x.UserId == cashierUserId, ct))
                throw new NotFoundException($"User {cashierUserId} was not found.");
        }
    }

    public async Task ValidateSalesLineAsync(
        int headerWarehouseId, int lineItemId, int lineWarehouseId, decimal qty, decimal freeQty, int? parentLineId, CancellationToken ct = default)
    {
        if (qty <= 0)
            throw new DomainException(ErrorCodes.Validation, "Sales line quantity must be greater than zero.", 422);
        if (freeQty < 0)
            throw new DomainException(ErrorCodes.Validation, "Sales free quantity cannot be negative.", 422);
        if (parentLineId is not null && parentLineId <= 0)
            throw new DomainException(ErrorCodes.Validation, "Parent line id must be positive when supplied.", 422);
        if (lineWarehouseId != headerWarehouseId)
            throw new DomainException(ErrorCodes.Validation, $"Sales line warehouse {lineWarehouseId} must match header warehouse {headerWarehouseId}.", 422);
        if (!await _items.Query().AnyAsync(x => x.ItemId == lineItemId, ct))
            throw new NotFoundException($"Item {lineItemId} was not found.");
        if (!await _warehouses.Query().AnyAsync(x => x.WarehouseId == lineWarehouseId, ct))
            throw new NotFoundException($"Warehouse {lineWarehouseId} was not found.");
    }

    public Task<SalesPriceResolution> ResolveLinePriceAsync(int itemId, int warehouseId, decimal? requestedUnitPrice, bool allowManualOverride, CancellationToken ct = default)
        => _pricing.ResolveAsync(itemId, warehouseId, requestedUnitPrice, allowManualOverride, ct);

    public SalesLineComputation ComputeLine(
        decimal qty,
        decimal freeQty,
        decimal unitPrice,
        SalesDiscountMode discountMode,
        decimal discountPct,
        decimal discountValue,
        decimal discountAmount,
        decimal taxPct,
        bool isFreeIssue)
    {
        var gross = qty * unitPrice;
        var discountTotal = isFreeIssue
            ? 0m
            : CalculateDiscount(gross, discountMode, discountPct, discountValue, discountAmount);
        var netUnit = qty > 0 ? (gross - discountTotal) / qty : 0m;
        var lineTotal = gross - discountTotal;
        var taxAmount = lineTotal * (taxPct / 100m);
        return new SalesLineComputation(gross, discountTotal, netUnit, lineTotal, taxAmount);
    }

    public async Task<bool> IsStockedItemAsync(int itemId, CancellationToken ct = default)
        => await _items.Query().AsNoTracking()
            .Where(x => x.ItemId == itemId)
            .Select(x => x.StockNature == StockNature.Stocked)
            .FirstAsync(ct);

    private static decimal CalculateDiscount(decimal gross, SalesDiscountMode mode, decimal discountPct, decimal discountValue, decimal legacyDiscountAmount)
    {
        var computed = mode == SalesDiscountMode.Amount
            ? discountValue
            : gross * (discountPct / 100m);

        if (computed <= 0m && legacyDiscountAmount > 0m)
            computed = legacyDiscountAmount;

        return Math.Min(gross, Math.Max(0m, computed));
    }
}
