using foodmarket.Application.Catalog;
using foodmarket.Application.Common;
using foodmarket.Domain.Catalog;
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace foodmarket.Api.Controllers.SuperAdmin;

/// <summary>SuperAdmin: CRUD над глобальными единицами измерения (Phase5c).
/// GET отдаёт все globals (active+inactive); POST/PUT всегда устанавливают
/// OrganizationId=NULL; DELETE — soft (IsActive=false), а если на единицу
/// ссылаются продукты или активные org-junction'ы — 409 со списком орг,
/// чтобы они сначала отвязались.</summary>
[ApiController]
[Authorize(Roles = "SuperAdmin")]
[Route("api/super-admin/units-of-measure")]
public class SuperAdminUnitsOfMeasureController : ControllerBase
{
    private readonly AppDbContext _db;

    public SuperAdminUnitsOfMeasureController(AppDbContext db) => _db = db;

    [HttpGet]
    public async Task<ActionResult<PagedResult<UnitOfMeasureDto>>> List(
        [FromQuery] PagedRequest req, CancellationToken ct)
    {
        var q = _db.UnitsOfMeasure
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(u => u.OrganizationId == null);

        if (!string.IsNullOrWhiteSpace(req.Search))
        {
            var s = req.Search.Trim().ToLower();
            q = q.Where(u => u.Name.ToLower().Contains(s) || u.Code.ToLower().Contains(s));
        }
        var total = await q.CountAsync(ct);
        q = (req.Sort, req.Desc) switch
        {
            ("code", false) => q.OrderBy(u => u.Code),
            ("code", true) => q.OrderByDescending(u => u.Code),
            ("name", true) => q.OrderByDescending(u => u.Name),
            _ => q.OrderBy(u => u.Name),
        };
        var items = await q
            .Skip(req.Skip).Take(req.Take)
            .Select(u => new UnitOfMeasureDto(
                u.Id, u.Code, u.Name, u.OrganizationId, u.IsActive, true))
            .ToListAsync(ct);
        return new PagedResult<UnitOfMeasureDto> { Items = items, Total = total, Page = req.Page, PageSize = req.Take };
    }

    [HttpGet("{id:guid}")]
    public async Task<ActionResult<UnitOfMeasureDto>> Get(Guid id, CancellationToken ct)
    {
        var u = await _db.UnitsOfMeasure
            .IgnoreQueryFilters()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == id && x.OrganizationId == null, ct);
        return u is null
            ? NotFound()
            : new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.OrganizationId, u.IsActive, true);
    }

    [HttpPost]
    public async Task<ActionResult<UnitOfMeasureDto>> Create([FromBody] UnitOfMeasureInput input, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(input.Code) || string.IsNullOrWhiteSpace(input.Name))
            return BadRequest(new { error = "Code и Name обязательны." });

        // Унiqueness среди active globals (filtered index в БД, но проверяем
        // и здесь чтобы вернуть осмысленный 409).
        var dup = await _db.UnitsOfMeasure.IgnoreQueryFilters()
            .AnyAsync(u => u.OrganizationId == null && u.IsActive && u.Code == input.Code, ct);
        if (dup) return Conflict(new { error = $"Код «{input.Code}» уже используется." });

        var e = new UnitOfMeasure
        {
            OrganizationId = null,
            Code = input.Code.Trim(),
            Name = input.Name.Trim(),
            IsActive = true,
        };
        _db.UnitsOfMeasure.Add(e);
        await _db.SaveChangesAsync(ct);
        return CreatedAtAction(nameof(Get), new { id = e.Id },
            new UnitOfMeasureDto(e.Id, e.Code, e.Name, e.OrganizationId, e.IsActive, true));
    }

    [HttpPut("{id:guid}")]
    public async Task<IActionResult> Update(Guid id, [FromBody] UnitOfMeasureInput input, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(input.Code) || string.IsNullOrWhiteSpace(input.Name))
            return BadRequest(new { error = "Code и Name обязательны." });

        var e = await _db.UnitsOfMeasure
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(x => x.Id == id && x.OrganizationId == null, ct);
        if (e is null) return NotFound();

        if (e.Code != input.Code.Trim())
        {
            var dup = await _db.UnitsOfMeasure.IgnoreQueryFilters()
                .AnyAsync(u => u.OrganizationId == null && u.IsActive && u.Code == input.Code && u.Id != id, ct);
            if (dup) return Conflict(new { error = $"Код «{input.Code}» уже используется." });
        }

        e.Code = input.Code.Trim();
        e.Name = input.Name.Trim();
        await _db.SaveChangesAsync(ct);
        return NoContent();
    }

    /// <summary>Soft-delete: IsActive=false. Если на единицу ссылаются
    /// продукты или активные org-junction'ы — 409 со списком орг (до 10),
    /// чтобы SuperAdmin понимал кого нужно сначала перевести.</summary>
    [HttpDelete("{id:guid}")]
    public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
    {
        var e = await _db.UnitsOfMeasure
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(x => x.Id == id && x.OrganizationId == null, ct);
        if (e is null) return NotFound();

        var productCount = await _db.Products
            .IgnoreQueryFilters()
            .CountAsync(p => p.UnitOfMeasureId == id, ct);

        var orgsUsing = await _db.OrgUnitsOfMeasure
            .IgnoreQueryFilters()
            .Where(j => j.UnitOfMeasureId == id)
            .Join(_db.Organizations.IgnoreQueryFilters(),
                j => j.OrganizationId,
                o => o.Id,
                (j, o) => o.Name)
            .OrderBy(n => n)
            .Take(10)
            .ToListAsync(ct);

        if (productCount > 0 || orgsUsing.Count > 0)
        {
            return Conflict(new
            {
                error = $"Единица используется: {productCount} товар(ов), {orgsUsing.Count} организаций. Сначала отключите её у этих орг.",
                productCount,
                organizations = orgsUsing,
            });
        }

        e.IsActive = false;
        await _db.SaveChangesAsync(ct);
        return NoContent();
    }
}
