using Apollinare.API.Hubs;
using Apollinare.Domain.Entities;
using Apollinare.Domain.Enums;
using Apollinare.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Apollinare.API.Controllers;

[ApiController]
[Route("api/[controller]")]
public class EventiController : ControllerBase
{
    private readonly AppollinareDbContext _context;
    private readonly DataNotificationService _notifier;

    public EventiController(AppollinareDbContext context, DataNotificationService notifier)
    {
        _context = context;
        _notifier = notifier;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Evento>>> GetEventi(
        [FromQuery] DateTime? dataInizio,
        [FromQuery] DateTime? dataFine,
        [FromQuery] StatoEvento? stato,
        [FromQuery] int? clienteId,
        [FromQuery] int? locationId)
    {
        var query = _context.Eventi
            .Include(e => e.Cliente)
            .Include(e => e.Location)
            .Include(e => e.TipoEvento)
            .AsQueryable();

        if (dataInizio.HasValue)
            query = query.Where(e => e.DataEvento >= dataInizio.Value);
        if (dataFine.HasValue)
            query = query.Where(e => e.DataEvento <= dataFine.Value);
        if (stato.HasValue)
            query = query.Where(e => e.Stato == stato.Value);
        if (clienteId.HasValue)
            query = query.Where(e => e.ClienteId == clienteId.Value);
        if (locationId.HasValue)
            query = query.Where(e => e.LocationId == locationId.Value);

        return await query.OrderBy(e => e.DataEvento).ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Evento>> GetEvento(int id)
    {
        var evento = await _context.Eventi
            .Include(e => e.Cliente)
            .Include(e => e.Location)
            .Include(e => e.TipoEvento)
            .Include(e => e.DettagliOspiti).ThenInclude(d => d.TipoOspite)
            .Include(e => e.DettagliPrelievo).ThenInclude(d => d.Articolo)
            .Include(e => e.DettagliRisorse).ThenInclude(d => d.Risorsa)
            .Include(e => e.Acconti)
            .Include(e => e.AltriCosti)
            .Include(e => e.Degustazioni)
            .FirstOrDefaultAsync(e => e.Id == id);

        if (evento == null)
            return NotFound();

        return evento;
    }

    [HttpPost]
    public async Task<ActionResult<Evento>> CreateEvento(Evento evento)
    {
        evento.CreatedAt = DateTime.UtcNow;
        evento.Codice = await GeneraCodiceEvento();

        _context.Eventi.Add(evento);
        await _context.SaveChangesAsync();
        await _notifier.NotifyCreated("eventi", evento);

        return CreatedAtAction(nameof(GetEvento), new { id = evento.Id }, evento);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateEvento(int id, Evento evento)
    {
        if (id != evento.Id)
            return BadRequest();

        evento.UpdatedAt = DateTime.UtcNow;
        _context.Entry(evento).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
            await _notifier.NotifyUpdated("eventi", evento);
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!await EventoExists(id))
                return NotFound();
            throw;
        }

        return Ok(evento);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteEvento(int id)
    {
        var evento = await _context.Eventi.FindAsync(id);
        if (evento == null)
            return NotFound();

        _context.Eventi.Remove(evento);
        await _context.SaveChangesAsync();
        await _notifier.NotifyDeleted("eventi", id);

        return NoContent();
    }

    [HttpPost("{id}/duplica")]
    public async Task<ActionResult<Evento>> DuplicaEvento(int id)
    {
        var eventoOriginale = await _context.Eventi
            .Include(e => e.DettagliOspiti)
            .Include(e => e.DettagliPrelievo)
            .Include(e => e.DettagliRisorse)
            .Include(e => e.AltriCosti)
            .AsNoTracking()
            .FirstOrDefaultAsync(e => e.Id == id);

        if (eventoOriginale == null)
            return NotFound();

        var nuovoEvento = new Evento
        {
            Codice = await GeneraCodiceEvento(),
            DataEvento = eventoOriginale.DataEvento,
            OraInizio = eventoOriginale.OraInizio,
            OraFine = eventoOriginale.OraFine,
            ClienteId = eventoOriginale.ClienteId,
            LocationId = eventoOriginale.LocationId,
            TipoEventoId = eventoOriginale.TipoEventoId,
            Stato = StatoEvento.Scheda,
            Descrizione = $"Copia di {eventoOriginale.Descrizione}",
            NumeroOspiti = eventoOriginale.NumeroOspiti,
            NumeroOspitiAdulti = eventoOriginale.NumeroOspitiAdulti,
            NumeroOspitiBambini = eventoOriginale.NumeroOspitiBambini,
            NumeroOspitiSeduti = eventoOriginale.NumeroOspitiSeduti,
            NumeroOspitiBuffet = eventoOriginale.NumeroOspitiBuffet,
            CostoTotale = eventoOriginale.CostoTotale,
            CostoPersona = eventoOriginale.CostoPersona,
            NoteInterne = eventoOriginale.NoteInterne,
            NoteCucina = eventoOriginale.NoteCucina,
            NoteAllestimento = eventoOriginale.NoteAllestimento,
            CreatedAt = DateTime.UtcNow
        };

        _context.Eventi.Add(nuovoEvento);
        await _context.SaveChangesAsync();

        // Duplica dettagli
        foreach (var ospite in eventoOriginale.DettagliOspiti)
        {
            _context.EventiDettaglioOspiti.Add(new EventoDettaglioOspiti
            {
                EventoId = nuovoEvento.Id,
                TipoOspiteId = ospite.TipoOspiteId,
                Numero = ospite.Numero,
                CostoUnitario = ospite.CostoUnitario,
                Sconto = ospite.Sconto,
                Ordine = ospite.Ordine,
                Note = ospite.Note
            });
        }

        foreach (var prelievo in eventoOriginale.DettagliPrelievo)
        {
            _context.EventiDettaglioPrelievo.Add(new EventoDettaglioPrelievo
            {
                EventoId = nuovoEvento.Id,
                ArticoloId = prelievo.ArticoloId,
                QtaRichiesta = prelievo.QtaRichiesta,
                QtaCalcolata = prelievo.QtaCalcolata,
                Note = prelievo.Note
            });
        }

        foreach (var risorsa in eventoOriginale.DettagliRisorse)
        {
            _context.EventiDettaglioRisorse.Add(new EventoDettaglioRisorsa
            {
                EventoId = nuovoEvento.Id,
                RisorsaId = risorsa.RisorsaId,
                OraInizio = risorsa.OraInizio,
                OraFine = risorsa.OraFine,
                Ruolo = risorsa.Ruolo,
                Note = risorsa.Note
            });
        }

        foreach (var costo in eventoOriginale.AltriCosti)
        {
            _context.EventiAltriCosti.Add(new EventoAltroCosto
            {
                EventoId = nuovoEvento.Id,
                Descrizione = costo.Descrizione,
                CostoUnitario = costo.CostoUnitario,
                Quantita = costo.Quantita,
                Ordine = costo.Ordine,
                ApplicaIva = costo.ApplicaIva,
                AliquotaIva = costo.AliquotaIva
            });
        }

        await _context.SaveChangesAsync();
        await _notifier.NotifyCreated("eventi", nuovoEvento);

        return CreatedAtAction(nameof(GetEvento), new { id = nuovoEvento.Id }, nuovoEvento);
    }

    [HttpPost("{id}/cambia-stato")]
    public async Task<IActionResult> CambiaStato(int id, [FromBody] CambiaStatoRequest request)
    {
        var evento = await _context.Eventi.FindAsync(id);
        if (evento == null)
            return NotFound();

        evento.Stato = request.NuovoStato;
        evento.UpdatedAt = DateTime.UtcNow;

        if (request.NuovoStato == StatoEvento.Confermato)
            evento.Confermato = true;

        await _context.SaveChangesAsync();
        await _notifier.NotifyUpdated("eventi", evento);

        return Ok(evento);
    }

    [HttpPost("{id}/ricalcola-quantita")]
    public async Task<IActionResult> RicalcolaQuantita(int id)
    {
        var evento = await _context.Eventi
            .Include(e => e.DettagliOspiti)
            .Include(e => e.DettagliPrelievo)
                .ThenInclude(p => p.Articolo)
                    .ThenInclude(a => a!.Categoria)
            .FirstOrDefaultAsync(e => e.Id == id);

        if (evento == null)
            return NotFound();

        // Calcola totale ospiti per tipo
        var totaleAdulti = evento.DettagliOspiti
            .Where(o => o.TipoOspite?.Codice == "ADULTO")
            .Sum(o => o.Numero);
        var totaleBambini = evento.DettagliOspiti
            .Where(o => o.TipoOspite?.Codice == "BAMBINO")
            .Sum(o => o.Numero);

        evento.NumeroOspitiAdulti = totaleAdulti;
        evento.NumeroOspitiBambini = totaleBambini;
        evento.NumeroOspiti = totaleAdulti + totaleBambini;

        // Ricalcola quantità prelievo usando coefficienti
        foreach (var prelievo in evento.DettagliPrelievo)
        {
            if (prelievo.Articolo?.Categoria != null)
            {
                var cat = prelievo.Articolo.Categoria;
                var qtaStdA = prelievo.Articolo.QtaStdA ?? 0;
                var qtaStdB = prelievo.Articolo.QtaStdB ?? 0;
                var qtaStdS = prelievo.Articolo.QtaStdS ?? 0;

                decimal qtaCalcolata = 0;

                if (evento.NumeroOspitiSeduti > 0)
                    qtaCalcolata += evento.NumeroOspitiSeduti.Value * qtaStdS * cat.CoeffS;
                else if (evento.NumeroOspitiBuffet > 0)
                    qtaCalcolata += evento.NumeroOspitiBuffet.Value * qtaStdB * cat.CoeffB;
                else
                    qtaCalcolata += (evento.NumeroOspiti ?? 0) * qtaStdA * cat.CoeffA;

                prelievo.QtaCalcolata = qtaCalcolata;
            }
        }

        await _context.SaveChangesAsync();

        return Ok(evento);
    }

    [HttpPost("{id}/ricalcola-acconti")]
    public async Task<IActionResult> RicalcolaAcconti(int id)
    {
        var evento = await _context.Eventi
            .Include(e => e.Acconti)
            .FirstOrDefaultAsync(e => e.Id == id);

        if (evento == null)
            return NotFound();

        evento.TotaleAcconti = evento.Acconti.Sum(a => a.Importo);
        evento.Saldo = (evento.CostoTotale ?? 0) - (evento.TotaleAcconti ?? 0);
        evento.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        return Ok(evento);
    }

    [HttpGet("calendario")]
    public async Task<ActionResult<IEnumerable<object>>> GetCalendario(
        [FromQuery] DateTime dataInizio,
        [FromQuery] DateTime dataFine)
    {
        var eventi = await _context.Eventi
            .Include(e => e.Cliente)
            .Include(e => e.Location)
            .Include(e => e.TipoEvento)
            .Where(e => e.DataEvento >= dataInizio && e.DataEvento <= dataFine)
            .Select(e => new
            {
                e.Id,
                e.Codice,
                title = e.Descrizione ?? $"{e.TipoEvento!.Descrizione} - {e.Cliente!.RagioneSociale}",
                start = e.DataEvento.Add(e.OraInizio ?? TimeSpan.Zero),
                end = e.DataEvento.Add(e.OraFine ?? TimeSpan.FromHours(23)),
                e.Stato,
                color = e.Stato == StatoEvento.Confermato ? "#4caf50" :
                        e.Stato == StatoEvento.Preventivo ? "#ff9800" : "#9e9e9e",
                e.NumeroOspiti,
                cliente = e.Cliente!.RagioneSociale,
                location = e.Location!.Nome
            })
            .ToListAsync();

        return Ok(eventi);
    }

    private async Task<string> GeneraCodiceEvento()
    {
        var anno = DateTime.Now.Year;
        var ultimoEvento = await _context.Eventi
            .Where(e => e.Codice != null && e.Codice.StartsWith($"EV{anno}"))
            .OrderByDescending(e => e.Codice)
            .FirstOrDefaultAsync();

        int numero = 1;
        if (ultimoEvento?.Codice != null)
        {
            var parte = ultimoEvento.Codice.Replace($"EV{anno}", "");
            if (int.TryParse(parte, out int n))
                numero = n + 1;
        }

        return $"EV{anno}{numero:D3}";
    }

    private async Task<bool> EventoExists(int id)
    {
        return await _context.Eventi.AnyAsync(e => e.Id == id);
    }
}

public class CambiaStatoRequest
{
    public StatoEvento NuovoStato { get; set; }
}
