using Apollinare.Domain.Entities;
using Apollinare.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace Apollinare.API.Services;

public class EventoCostiService
{
    private readonly AppollinareDbContext _context;
    private const decimal IVA_DEFAULT = 10m; // 10% IVA

    public EventoCostiService(AppollinareDbContext context)
    {
        _context = context;
    }

    /// <summary>
    /// Calcola il riepilogo completo dei costi per un evento
    /// </summary>
    public async Task<EventoCostiRiepilogo> CalcolaCostiEvento(int eventoId)
    {
        var evento = await _context.Eventi
            .Include(e => e.DettagliOspiti)
            .Include(e => e.DettagliRisorse)
            .Include(e => e.AltriCosti)
            .Include(e => e.Degustazioni)
            .Include(e => e.Acconti)
            .FirstOrDefaultAsync(e => e.Id == eventoId);

        if (evento == null)
            throw new ArgumentException($"Evento {eventoId} non trovato");

        var riepilogo = new EventoCostiRiepilogo();

        // 1. Costo Ospiti (con IVA 10%)
        riepilogo.CostoOspiti = CalcolaCostoOspiti(evento.DettagliOspiti);
        riepilogo.CostoOspitiConIva = riepilogo.CostoOspiti * (1 + IVA_DEFAULT / 100);

        // 2. Costo Risorse (personale)
        riepilogo.CostoRisorse = CalcolaCostoRisorse(evento.DettagliRisorse);

        // 3. Costo Degustazioni (detraibile)
        riepilogo.CostoDegustazioni = CalcolaCostoDegustazioni(evento.Degustazioni);

        // 4. Altri Costi (con IVA se applicabile)
        var (altriCosti, altriCostiConIva) = CalcolaAltriCosti(evento.AltriCosti);
        riepilogo.AltriCosti = altriCosti;
        riepilogo.AltriCostiConIva = altriCostiConIva;

        // 5. Totale = Ospiti + Risorse - Degustazioni + AltriCosti (tutto con IVA)
        riepilogo.TotaleLordo = riepilogo.CostoOspitiConIva +
                                riepilogo.CostoRisorse +
                                riepilogo.AltriCostiConIva;

        riepilogo.TotaleNettoDegustazioni = riepilogo.CostoDegustazioni;
        riepilogo.CostoTotale = riepilogo.TotaleLordo - riepilogo.TotaleNettoDegustazioni;

        // 6. Acconti e Saldo
        riepilogo.TotaleAccontiPagati = evento.Acconti
            .Where(a => a.DataPagamento.HasValue)
            .Sum(a => a.Importo);

        riepilogo.TotaleAccontiPrevisti = evento.Acconti.Sum(a => a.Importo);
        riepilogo.Saldo = riepilogo.CostoTotale - riepilogo.TotaleAccontiPagati;

        // 7. Costo per persona
        var numeroOspiti = evento.NumeroOspiti ?? evento.DettagliOspiti.Sum(o => o.Numero);
        riepilogo.NumeroOspiti = numeroOspiti;
        riepilogo.CostoPerPersona = numeroOspiti > 0 ? riepilogo.CostoTotale / numeroOspiti : 0;

        // Dettagli per voce
        riepilogo.DettaglioAltriCosti = evento.AltriCosti
            .OrderBy(c => c.Ordine)
            .Select(c => new DettaglioCosto
            {
                Id = c.Id,
                Descrizione = c.Descrizione,
                CostoUnitario = c.CostoUnitario,
                Quantita = c.Quantita,
                Totale = c.Totale,
                TotaleConIva = c.TotaleConIva,
                ApplicaIva = c.ApplicaIva,
                AliquotaIva = c.AliquotaIva
            }).ToList();

        riepilogo.DettaglioAcconti = evento.Acconti
            .OrderBy(a => a.Ordine)
            .Select(a => new DettaglioAcconto
            {
                Id = a.Id,
                Descrizione = a.Descrizione,
                Importo = a.Importo,
                DataPagamento = a.DataPagamento,
                Pagato = a.Pagato,
                AConferma = a.AConferma,
                Ordine = a.Ordine
            }).ToList();

        return riepilogo;
    }

    private decimal CalcolaCostoOspiti(ICollection<EventoDettaglioOspiti> dettagli)
    {
        return dettagli.Sum(d => d.CostoTotale);
    }

    private decimal CalcolaCostoRisorse(ICollection<EventoDettaglioRisorsa> dettagli)
    {
        return dettagli.Sum(d => d.Costo ?? 0);
    }

    private decimal CalcolaCostoDegustazioni(ICollection<EventoDegustazione> degustazioni)
    {
        return degustazioni.Where(d => d.Detraibile).Sum(d => d.CostoTotale);
    }

    private (decimal totale, decimal totaleConIva) CalcolaAltriCosti(ICollection<EventoAltroCosto> costi)
    {
        var totale = costi.Sum(c => c.Totale);
        var totaleConIva = costi.Sum(c => c.TotaleConIva);
        return (totale, totaleConIva);
    }

    /// <summary>
    /// Ricalcola gli acconti automatici secondo la logica Oracle:
    /// - 30% Prima caparra (a conferma)
    /// - 50% Seconda caparra (60 giorni prima)
    /// - 20% Saldo finale
    /// </summary>
    public async Task RicalcolaAcconti(int eventoId)
    {
        var riepilogo = await CalcolaCostiEvento(eventoId);
        var evento = await _context.Eventi
            .Include(e => e.Acconti)
            .FirstAsync(e => e.Id == eventoId);

        var totaleEvento = riepilogo.CostoTotale;

        // Verifica se ci sono acconti già pagati
        var accontiPagati = evento.Acconti
            .Where(a => a.DataPagamento.HasValue && (a.Ordine == 10 || a.Ordine == 20))
            .ToList();

        if (accontiPagati.Any())
        {
            // Ricalcola solo i saldi non pagati
            await RicalcolaSaldi(evento, totaleEvento);
        }
        else
        {
            // Ricrea tutti gli acconti
            await RicreaAccontiStandard(evento, totaleEvento);
        }

        // Aggiorna i totali sull'evento
        evento.CostoTotale = totaleEvento;
        evento.CostoPersona = riepilogo.CostoPerPersona;
        evento.TotaleAcconti = riepilogo.TotaleAccontiPagati;
        evento.Saldo = riepilogo.Saldo;

        await _context.SaveChangesAsync();
    }

    private async Task RicreaAccontiStandard(Evento evento, decimal totaleEvento)
    {
        // Rimuovi acconti standard esistenti (ordine 10, 20, 30)
        var accontiDaRimuovere = evento.Acconti
            .Where(a => a.Ordine == 10 || a.Ordine == 20 || a.Ordine == 30)
            .ToList();

        foreach (var acconto in accontiDaRimuovere)
        {
            _context.Set<EventoAcconto>().Remove(acconto);
        }

        // Crea nuovi acconti
        var primoAcconto = new EventoAcconto
        {
            EventoId = evento.Id,
            Descrizione = "PRIMA CAPARRA (art.7 punto A del contratto) a conferma evento",
            Importo = Math.Round(totaleEvento * 0.30m, 2),
            AConferma = true,
            Ordine = 10
        };

        var secondoAcconto = new EventoAcconto
        {
            EventoId = evento.Id,
            Descrizione = "SECONDA CAPARRA (art. 7 punto B - circa 60 giorni prima dell'evento)",
            Importo = Math.Round(totaleEvento * 0.50m, 2),
            AConferma = false,
            Ordine = 20
        };

        var terzoAcconto = new EventoAcconto
        {
            EventoId = evento.Id,
            Descrizione = "SALDO A RICEVIMENTO CONSUNTIVO (art.7 punto c del contratto)",
            Importo = Math.Round(totaleEvento * 0.20m, 2),
            AConferma = false,
            Ordine = 30
        };

        _context.Set<EventoAcconto>().AddRange(primoAcconto, secondoAcconto, terzoAcconto);
    }

    private async Task RicalcolaSaldi(Evento evento, decimal totaleEvento)
    {
        decimal primoPagato = 0, secondoPagato = 0, terzoPagato = 0;

        // Recupera acconti pagati
        foreach (var acconto in evento.Acconti.Where(a => a.DataPagamento.HasValue))
        {
            switch (acconto.Ordine)
            {
                case 10: primoPagato = acconto.Importo; break;
                case 20: secondoPagato = acconto.Importo; break;
                case 30: terzoPagato = acconto.Importo; break;
            }
        }

        // Se primo non pagato, calcola standard
        if (primoPagato == 0)
            primoPagato = totaleEvento * 0.30m;

        // Se secondo non pagato, calcola proporzionalmente
        if (secondoPagato == 0)
        {
            var rimanente = totaleEvento - primoPagato;
            secondoPagato = rimanente * (0.50m / 0.70m); // 50% del 70% rimanente
        }

        // Terzo è sempre il saldo rimanente
        if (terzoPagato == 0)
            terzoPagato = totaleEvento - primoPagato - secondoPagato;

        // Aggiorna o crea gli acconti non pagati
        await AggiornaOCreaAcconto(evento, 20, secondoPagato,
            "SECONDA CAPARRA (art. 7 punto B - circa 60 giorni prima dell'evento)");
        await AggiornaOCreaAcconto(evento, 30, terzoPagato,
            "SALDO A RICEVIMENTO CONSUNTIVO (art.7 punto c del contratto)");
    }

    private async Task AggiornaOCreaAcconto(Evento evento, int ordine, decimal importo, string descrizione)
    {
        var acconto = evento.Acconti.FirstOrDefault(a => a.Ordine == ordine);

        if (importo > 0)
        {
            if (acconto != null && !acconto.DataPagamento.HasValue)
            {
                acconto.Importo = Math.Round(importo, 2);
            }
            else if (acconto == null)
            {
                _context.Set<EventoAcconto>().Add(new EventoAcconto
                {
                    EventoId = evento.Id,
                    Descrizione = descrizione,
                    Importo = Math.Round(importo, 2),
                    Ordine = ordine
                });
            }
        }
        else if (acconto != null && !acconto.DataPagamento.HasValue)
        {
            _context.Set<EventoAcconto>().Remove(acconto);
        }
    }
}

// DTOs per il riepilogo costi
public class EventoCostiRiepilogo
{
    public decimal CostoOspiti { get; set; }
    public decimal CostoOspitiConIva { get; set; }
    public decimal CostoRisorse { get; set; }
    public decimal CostoDegustazioni { get; set; }
    public decimal AltriCosti { get; set; }
    public decimal AltriCostiConIva { get; set; }
    public decimal TotaleLordo { get; set; }
    public decimal TotaleNettoDegustazioni { get; set; }
    public decimal CostoTotale { get; set; }
    public decimal TotaleAccontiPagati { get; set; }
    public decimal TotaleAccontiPrevisti { get; set; }
    public decimal Saldo { get; set; }
    public int NumeroOspiti { get; set; }
    public decimal CostoPerPersona { get; set; }

    public List<DettaglioCosto> DettaglioAltriCosti { get; set; } = new();
    public List<DettaglioAcconto> DettaglioAcconti { get; set; } = new();
}

public class DettaglioCosto
{
    public int Id { get; set; }
    public string Descrizione { get; set; } = string.Empty;
    public decimal CostoUnitario { get; set; }
    public decimal Quantita { get; set; }
    public decimal Totale { get; set; }
    public decimal TotaleConIva { get; set; }
    public bool ApplicaIva { get; set; }
    public decimal AliquotaIva { get; set; }
}

public class DettaglioAcconto
{
    public int Id { get; set; }
    public string? Descrizione { get; set; }
    public decimal Importo { get; set; }
    public DateTime? DataPagamento { get; set; }
    public bool Pagato { get; set; }
    public bool AConferma { get; set; }
    public int Ordine { get; set; }
}
