using System.ComponentModel.DataAnnotations;
using System.Globalization;
using DoliMiddlewareApi.Dtos;
using DoliMiddlewareApi.Dtos.Dolibarr;
using DoliMiddlewareApi.Dtos.command;
using DoliMiddlewareApi.Dtos.query;
using DoliMiddlewareApi.Exceptions;
using DoliMiddlewareApi.Mappers;
using DoliMiddlewareApi.Services.Clients;
using DoliMiddlewareApi.Services.Notifications;

namespace DoliMiddlewareApi.Services;

public class SupplierInvoiceService(IDolibarrApiClient apiClient, INotificationService notifications)
{
    public async Task<List<SupplierInvoiceDto>> GetInvoicesAsync(int limit, int page, string? status)
    {
        var endpoint = $"supplierinvoices?limit={limit}&page={page - 1}";
        if (!string.IsNullOrEmpty(status))
            endpoint += $"&status={status}";

        var list = await apiClient.GetCollectionAsync<SupplierInvoiceResponse>(endpoint);
        var dtos = list.Select(SupplierInvoiceMapper.MapToDto).ToList();

        if (dtos.Count == 0) return dtos;

        var supplierIds = dtos.Select(d => d.SupplierId).Distinct().ToList();
        var names = await GetSupplierNamesAsync(supplierIds);
        foreach (var dto in dtos)
            dto.SupplierName = names.GetValueOrDefault(dto.SupplierId);

        return dtos;
    }

    public async Task<SupplierInvoiceDetailDto> GetInvoiceAsync(int id)
    {
        var data = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
        var dto = SupplierInvoiceMapper.MapToDetailDto(data);

        if (dto.SupplierId > 0)
        {
            try
            {
                var supplier = await apiClient.GetResourceAsync<ClientResponse>($"thirdparties/{dto.SupplierId}");
                dto.SupplierName = supplier?.name;
            }
            catch { /* supplier lookup is best-effort */ }
        }

        return dto;
    }

    public async Task<int> CreateInvoiceAsync(CreateSupplierInvoiceDto dto)
    {
        var payload = new
        {
            socid = dto.SupplierId.ToString(),
            ref_supplier = dto.SupplierRef,
            date = ((DateTimeOffset)dto.Date).ToUnixTimeSeconds().ToString(),
            date_lim_reglement = dto.ExpireDate.HasValue
                ? ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds().ToString()
                : null,
            note_public = dto.NotePublic,
            note_private = dto.NotePrivate,
            lines = dto.Lines.Select(l => new
            {
                desc = l.Description,
                qty = l.Quantity.ToString(CultureInfo.InvariantCulture),
                subprice = l.UnitPrice.ToString(CultureInfo.InvariantCulture),
                tva_tx = l.TaxRate.ToString(CultureInfo.InvariantCulture)
            }).ToArray()
        };

        var result = await apiClient.PostAsync("supplierinvoices", payload);
        return int.TryParse(result.Trim('"'), out int id) ? id : 0;
    }

    public async Task UpdateInvoiceAsync(int id, UpdateSupplierInvoiceDto dto)
    {
        var current = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");

        if (dto.SupplierRef != null) current.ref_supplier = dto.SupplierRef;
        if (dto.NotePublic != null) current.note_public = dto.NotePublic;
        if (dto.NotePrivate != null) current.note_private = dto.NotePrivate;
        if (dto.ExpireDate.HasValue)
            current.date_lim_reglement = ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds();

        await apiClient.PutAsync($"supplierinvoices/{id}", current);
    }

    public async Task DeleteInvoiceAsync(int id)
    {
        var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
        if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden eliminar facturas de proveedor en borrador");

        await apiClient.DeleteAsync($"supplierinvoices/{id}");
    }

    public async Task ChangeStatusAsync(int id, string status)
    {
        var normalized = status.Trim().ToLowerInvariant();

        if (normalized == "paid")
        {
            var inv = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
            var remain = double.TryParse(inv.remaintopay, System.Globalization.NumberStyles.Any,
                System.Globalization.CultureInfo.InvariantCulture, out var r) ? r : 0;
            var amount = remain > 0 ? remain
                : double.TryParse(inv.total_ttc, System.Globalization.NumberStyles.Any,
                    System.Globalization.CultureInfo.InvariantCulture, out var t) ? t : 0;

            var unixNow = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
            var payBody = new Dictionary<string, object>
            {
                ["datepaye"]          = unixNow,
                ["payment_mode_id"]   = 6,
                ["closepaidinvoices"] = "yes",
                ["accountid"]         = 1,
                ["amount"]            = amount
            };
            await apiClient.PostAsync($"supplierinvoices/{id}/payments", payBody);
            var paidInv = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
            await notifications.NotifyInvoiceStatusChangedAsync(id, paidInv.@ref ?? $"#{id}", "paid");
            return;
        }

        var endpoint = normalized switch
        {
            "draft"  => $"supplierinvoices/{id}/settodraft",
            "unpaid" => $"supplierinvoices/{id}/validate",
            _ => throw new ValidationException("Estado invalido. Usa: draft, unpaid, paid.")
        };

        await apiClient.PostAsync(endpoint, new { });
        var updatedInv = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
        await notifications.NotifyInvoiceStatusChangedAsync(id, updatedInv.@ref ?? $"#{id}", normalized);
    }

    public async Task<string> AddLineAsync(int invoiceId, CreateInvoiceLineDto dto)
    {
        var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{invoiceId}");
        if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden añadir líneas a facturas en borrador");

        var requestBody = new
        {
            desc = dto.Description,
            qty = dto.Quantity.ToString(CultureInfo.InvariantCulture),
            subprice = dto.UnitPrice.ToString(CultureInfo.InvariantCulture),
            tva_tx = dto.TaxRate.ToString(CultureInfo.InvariantCulture)
        };

        return await apiClient.PostAsync($"supplierinvoices/{invoiceId}/lines", requestBody);
    }

    public async Task UpdateLineAsync(int invoiceId, int lineId, UpdateInvoiceLineDto dto)
    {
        var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{invoiceId}");
        if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden modificar líneas de facturas en borrador");

        var existingLine = invoice.Lines?.FirstOrDefault(l => l.id == lineId.ToString());
        if (existingLine == null) throw new NotFoundException($"Línea {lineId} no encontrada en factura {invoiceId}");

        var requestBody = new Dictionary<string, object?>
        {
            ["desc"] = dto.Description ?? existingLine.description ?? existingLine.desc,
            ["qty"] = dto.Quantity?.ToString(CultureInfo.InvariantCulture) ?? existingLine.qty,
            ["subprice"] = dto.UnitPrice?.ToString(CultureInfo.InvariantCulture) ?? existingLine.subprice,
            ["tva_tx"] = dto.TaxRate?.ToString(CultureInfo.InvariantCulture) ?? existingLine.tva_tx
        };

        await apiClient.PutAsync($"supplierinvoices/{invoiceId}/lines/{lineId}", requestBody);
    }

    public async Task DeleteLineAsync(int invoiceId, int lineId)
    {
        var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{invoiceId}");
        if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden eliminar líneas de facturas en borrador");

        await apiClient.DeleteAsync($"supplierinvoices/{invoiceId}/lines/{lineId}");
    }

    public async Task<List<InvoicePaymentDto>> GetPaymentsAsync(int invoiceId)
    {
        var dataList = await apiClient.GetCollectionAsync<InvoicePaymentResponse>($"supplierinvoices/{invoiceId}/payments");
        return dataList.Select(InvoiceMapper.MapToInvoicePaymentDto).ToList();
    }

    public async Task<int> AddPaymentAsync(int invoiceId, CreateInvoicePaymentDto dto)
    {
        var paymentModeId = dto.PaymentMethodId ?? dto.PaymentModeId;
        var requestBody = new
        {
            datepaye = dto.PaymentDate.ToString("yyyy-MM-dd"),
            paymentid = paymentModeId,
            closepaidinvoices = dto.ClosePaidInvoices,
            accountid = dto.AccountId,
            num_payment = dto.PaymentNumber,
            amount = dto.Amount?.ToString(CultureInfo.InvariantCulture)
        };
        var responseBody = await apiClient.PostAsync($"supplierinvoices/{invoiceId}/payments", requestBody);
        return int.TryParse(responseBody.Trim('"'), out int result) ? result : 0;
    }

    private async Task<Dictionary<int, string>> GetSupplierNamesAsync(List<int> ids)
    {
        var validIds = ids.Distinct().Where(id => id > 0).ToList();
        if (validIds.Count == 0) return [];

        var tasks = validIds.Select(async id =>
        {
            try { return await apiClient.GetResourceAsync<ClientResponse>($"thirdparties/{id}"); }
            catch { return null; }
        });

        var results = await Task.WhenAll(tasks);
        return results
            .Where(c => c is { id: not null })
            .ToDictionary(c => int.Parse(c!.id!), c => c!.name ?? "");
    }
}
