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.Catalog;

[ApiController]
[Authorize]
[Route("api/catalog/counterparties")]
public class CounterpartiesController : ControllerBase
{
    private readonly AppDbContext _db;

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

    [HttpGet]
    public async Task<ActionResult<PagedResult<CounterpartyDto>>> List(
        [FromQuery] PagedRequest req,
        CancellationToken ct)
    {
        var q = _db.Counterparties.Include(c => c.Country).AsNoTracking().AsQueryable();
        if (!string.IsNullOrWhiteSpace(req.Search))
        {
            var s = req.Search.Trim().ToLower();
            q = q.Where(c =>
                c.Name.ToLower().Contains(s) ||
                (c.LegalName != null && c.LegalName.ToLower().Contains(s)) ||
                (c.Bin != null && c.Bin.Contains(s)) ||
                (c.Iin != null && c.Iin.Contains(s)) ||
                (c.Phone != null && c.Phone.Contains(s)));
        }
        var total = await q.CountAsync(ct);
        q = (req.Sort, req.Desc) switch
        {
            ("type", false) => q.OrderBy(c => c.Type).ThenBy(c => c.Name),
            ("type", true) => q.OrderByDescending(c => c.Type).ThenBy(c => c.Name),
            ("country", false) => q.OrderBy(c => c.Country != null ? c.Country.Name : null).ThenBy(c => c.Name),
            ("country", true) => q.OrderByDescending(c => c.Country != null ? c.Country.Name : null).ThenBy(c => c.Name),
            ("legalName", false) => q.OrderBy(c => c.LegalName).ThenBy(c => c.Name),
            ("legalName", true) => q.OrderByDescending(c => c.LegalName).ThenBy(c => c.Name),
            ("phone", false) => q.OrderBy(c => c.Phone).ThenBy(c => c.Name),
            ("phone", true) => q.OrderByDescending(c => c.Phone).ThenBy(c => c.Name),
            ("name", true) => q.OrderByDescending(c => c.Name),
            _ => q.OrderBy(c => c.Name),
        };
        var items = await q
            .Skip(req.Skip).Take(req.Take)
            .Select(c => new CounterpartyDto(
                c.Id, c.Name, c.LegalName, c.Type,
                c.Bin, c.Iin, c.TaxNumber, c.CountryId, c.Country != null ? c.Country.Name : null,
                c.Address, c.Phone, c.Email,
                c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes))
            .ToListAsync(ct);
        return new PagedResult<CounterpartyDto> { Items = items, Total = total, Page = req.Page, PageSize = req.Take };
    }

    [HttpGet("{id:guid}")]
    public async Task<ActionResult<CounterpartyDto>> Get(Guid id, CancellationToken ct)
    {
        var c = await _db.Counterparties.Include(x => x.Country).AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct);
        return c is null ? NotFound() : new CounterpartyDto(
            c.Id, c.Name, c.LegalName, c.Type,
            c.Bin, c.Iin, c.TaxNumber, c.CountryId, c.Country?.Name,
            c.Address, c.Phone, c.Email,
            c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes);
    }

    [HttpPost, Authorize(Roles = "Admin,Storekeeper")]
    public async Task<ActionResult<CounterpartyDto>> Create([FromBody] CounterpartyInput input, CancellationToken ct)
    {
        if (NormalizePhoneOrError(input.Phone) is { } err) return BadRequest(new { error = err });
        var e = Apply(new Counterparty(), input);
        _db.Counterparties.Add(e);
        await _db.SaveChangesAsync(ct);
        return CreatedAtAction(nameof(Get), new { id = e.Id }, await ProjectAsync(e.Id, ct));
    }

    [HttpPut("{id:guid}"), Authorize(Roles = "Admin,Storekeeper")]
    public async Task<IActionResult> Update(Guid id, [FromBody] CounterpartyInput input, CancellationToken ct)
    {
        if (NormalizePhoneOrError(input.Phone) is { } err) return BadRequest(new { error = err });
        var e = await _db.Counterparties.FirstOrDefaultAsync(x => x.Id == id, ct);
        if (e is null) return NotFound();
        Apply(e, input);
        await _db.SaveChangesAsync(ct);
        return NoContent();
    }

    private static string? NormalizePhoneOrError(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw)) return null;
        return foodmarket.Application.Common.PhoneNormalization.IsValidOrEmpty(raw)
            ? null
            : foodmarket.Application.Common.PhoneNormalization.ErrorMessage;
    }

    [HttpDelete("{id:guid}"), Authorize(Roles = "Admin")]
    public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
    {
        var e = await _db.Counterparties.FirstOrDefaultAsync(x => x.Id == id, ct);
        if (e is null) return NotFound();
        _db.Counterparties.Remove(e);
        await _db.SaveChangesAsync(ct);
        return NoContent();
    }

    private static Counterparty Apply(Counterparty e, CounterpartyInput i)
    {
        e.Name = i.Name;
        e.LegalName = i.LegalName;
        e.Type = i.Type;
        e.Bin = i.Bin;
        e.Iin = i.Iin;
        e.TaxNumber = i.TaxNumber;
        e.CountryId = i.CountryId;
        e.Address = i.Address;
        e.Phone = foodmarket.Application.Common.PhoneNormalization.TryNormalizeKz(i.Phone);
        e.Email = i.Email;
        e.BankName = i.BankName;
        e.BankAccount = i.BankAccount;
        e.Bik = i.Bik;
        e.ContactPerson = i.ContactPerson;
        e.Notes = i.Notes;
        return e;
    }

    private async Task<CounterpartyDto> ProjectAsync(Guid id, CancellationToken ct)
    {
        var c = await _db.Counterparties.Include(x => x.Country).AsNoTracking().FirstAsync(x => x.Id == id, ct);
        return new CounterpartyDto(
            c.Id, c.Name, c.LegalName, c.Type,
            c.Bin, c.Iin, c.TaxNumber, c.CountryId, c.Country?.Name,
            c.Address, c.Phone, c.Email,
            c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes);
    }
}
