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

namespace ERPCore.Services;

/// <summary>
/// Brand master service (FR-MD-09). Enforces name uniqueness and optimistic concurrency
/// per docs/11-BACKEND-PHASE1.md §2.6.
/// </summary>
public sealed class BrandService : IBrandService
{
    private readonly IRepository<Brand> _brands;
    private readonly IUnitOfWork _uow;

    public BrandService(IRepository<Brand> brands, IUnitOfWork uow)
    {
        _brands = brands;
        _uow = uow;
    }

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

        var total = await q.CountAsync(ct);
        var rows = await q.OrderBy(b => b.Name)
            .Skip(query.Skip).Take(query.PageSize)
            .Select(b => new BrandDto(b.BrandId, b.Name, b.Status, b.CreatedAt, b.UpdatedAt))
            .ToListAsync(ct);

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

    public async Task<ETagged<BrandDto>?> GetAsync(int brandId, CancellationToken ct = default)
    {
        var brand = await _brands.Query().AsNoTracking()
            .FirstOrDefaultAsync(b => b.BrandId == brandId, ct);
        return brand is null ? null : new ETagged<BrandDto>(Map(brand), brand.RowVersion);
    }

    public async Task<ETagged<BrandDto>> CreateAsync(CreateBrandRequest request, CancellationToken ct = default)
    {
        var name = request.Name.Trim();
        if (await _brands.Query().AnyAsync(b => b.Name.ToLower() == name.ToLower(), ct))
            throw new ConflictException($"A brand named '{name}' already exists.");

        var brand = new Brand
        {
            Name = name,
            Status = EntityStatus.Active,
            CreatedAt = DateTime.UtcNow
        };

        await _brands.AddAsync(brand, ct);
        await _uow.SaveChangesAsync(ct);

        return new ETagged<BrandDto>(Map(brand), brand.RowVersion);
    }

    public async Task<ETagged<BrandDto>> UpdateAsync(
        int brandId, UpdateBrandRequest request, uint expectedRowVersion, CancellationToken ct = default)
    {
        var brand = await _brands.GetByIdAsync(brandId, ct)
            ?? throw new NotFoundException($"Brand {brandId} was not found.");

        if (brand.RowVersion != expectedRowVersion)
            throw new DomainException(ErrorCodes.ConcurrencyConflict, "The brand was modified by another request.", 412);

        var name = request.Name.Trim();
        if (!string.Equals(brand.Name, name, StringComparison.Ordinal)
            && await _brands.Query().AnyAsync(b => b.Name.ToLower() == name.ToLower() && b.BrandId != brandId, ct))
            throw new ConflictException($"A brand named '{name}' already exists.");

        brand.Name = name;
        brand.UpdatedAt = DateTime.UtcNow;

        try
        {
            await _uow.SaveChangesAsync(ct);
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new DomainException(ErrorCodes.ConcurrencyConflict, "The brand was modified by another request.", 412);
        }

        return new ETagged<BrandDto>(Map(brand), brand.RowVersion);
    }

    public async Task SetStatusAsync(int brandId, EntityStatus status, CancellationToken ct = default)
    {
        var brand = await _brands.GetByIdAsync(brandId, ct)
            ?? throw new NotFoundException($"Brand {brandId} was not found.");

        brand.Status = status;
        brand.UpdatedAt = DateTime.UtcNow;
        await _uow.SaveChangesAsync(ct);
    }

    private static BrandDto Map(Brand b) => new(b.BrandId, b.Name, b.Status, b.CreatedAt, b.UpdatedAt);
}
