using ERPCore.Common.Http;
using ERPCore.Domain.Entities;
using ERPCore.Dtos.Config;
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;

/// <summary>
/// Product configuration service (FR-MD-11) over the singleton row seeded by DataSeeder.
/// Reads are never gated — only writes consult the flags — so switching a feature off
/// leaves existing data readable (docs/11-BACKEND-PHASE1.md §2.8).
/// </summary>
public sealed class ProductConfigService : IProductConfigService
{
    private readonly IRepository<ProductConfig> _config;
    private readonly ICurrentUser _currentUser;
    private readonly IUnitOfWork _uow;

    public ProductConfigService(IRepository<ProductConfig> config, ICurrentUser currentUser, IUnitOfWork uow)
    {
        _config = config;
        _currentUser = currentUser;
        _uow = uow;
    }

    public async Task<ETagged<ProductConfigDto>> GetAsync(CancellationToken ct = default)
    {
        var config = await _config.Query().AsNoTracking()
            .FirstOrDefaultAsync(c => c.ConfigId == ProductConfig.SingletonId, ct)
            ?? throw new NotFoundException("Product configuration has not been seeded.");

        return new ETagged<ProductConfigDto>(Map(config), config.RowVersion);
    }

    public async Task<ETagged<ProductConfigDto>> UpdateAsync(
        UpdateProductConfigRequest request, uint expectedRowVersion, CancellationToken ct = default)
    {
        var config = await _config.GetByIdAsync(ProductConfig.SingletonId, ct)
            ?? throw new NotFoundException("Product configuration has not been seeded.");

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

        // Non-null by [Required] on the nullable bools — an omitted flag is a 400, so the
        // model binder has already rejected anything that would reach here with a null.
        config.SubcategoriesEnabled = request.SubcategoriesEnabled!.Value;
        config.BrandsEnabled = request.BrandsEnabled!.Value;
        config.ItemTypesEnabled = request.ItemTypesEnabled!.Value;
        config.UpdatedAt = DateTime.UtcNow;
        config.UpdatedBy = _currentUser.AuditUserId;

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

        return new ETagged<ProductConfigDto>(Map(config), config.RowVersion);
    }

    private static ProductConfigDto Map(ProductConfig c) => new(
        c.SubcategoriesEnabled, c.BrandsEnabled, c.ItemTypesEnabled, c.UpdatedAt, c.UpdatedBy);
}
