using System.Text.Json;
using Apollinare.Domain.Entities;
using Apollinare.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Apollinare.API.Controllers;

[ApiController]
[Route("api/virtual-datasets")]
public class VirtualDatasetsController : ControllerBase
{
    private readonly AppollinareDbContext _context;

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

    /// <summary>
    /// Ottiene tutti i virtual dataset
    /// </summary>
    [HttpGet]
    public async Task<ActionResult<List<VirtualDatasetDto>>> GetAll([FromQuery] string? categoria = null)
    {
        var query = _context.VirtualDatasets.Where(v => v.Attivo);

        if (!string.IsNullOrWhiteSpace(categoria))
        {
            query = query.Where(v => v.Categoria == categoria);
        }

        var datasets = await query
            .OrderBy(v => v.DisplayName)
            .Select(v => new VirtualDatasetDto
            {
                Id = v.Id,
                Nome = v.Nome,
                DisplayName = v.DisplayName,
                Descrizione = v.Descrizione,
                Icon = v.Icon,
                Categoria = v.Categoria,
                Configuration = DeserializeConfig(v.ConfigurationJson),
                Attivo = v.Attivo,
                CreatedAt = v.CreatedAt,
                UpdatedAt = v.UpdatedAt
            })
            .ToListAsync();

        return datasets;
    }

    /// <summary>
    /// Ottiene un virtual dataset per ID
    /// </summary>
    [HttpGet("{id}")]
    public async Task<ActionResult<VirtualDatasetDto>> GetById(int id)
    {
        var dataset = await _context.VirtualDatasets.FindAsync(id);
        if (dataset == null)
            return NotFound();

        return new VirtualDatasetDto
        {
            Id = dataset.Id,
            Nome = dataset.Nome,
            DisplayName = dataset.DisplayName,
            Descrizione = dataset.Descrizione,
            Icon = dataset.Icon,
            Categoria = dataset.Categoria,
            Configuration = DeserializeConfig(dataset.ConfigurationJson),
            Attivo = dataset.Attivo,
            CreatedAt = dataset.CreatedAt,
            UpdatedAt = dataset.UpdatedAt
        };
    }

    /// <summary>
    /// Ottiene un virtual dataset per nome
    /// </summary>
    [HttpGet("by-name/{nome}")]
    public async Task<ActionResult<VirtualDatasetDto>> GetByName(string nome)
    {
        var dataset = await _context.VirtualDatasets
            .FirstOrDefaultAsync(v => v.Nome == nome && v.Attivo);

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

        return new VirtualDatasetDto
        {
            Id = dataset.Id,
            Nome = dataset.Nome,
            DisplayName = dataset.DisplayName,
            Descrizione = dataset.Descrizione,
            Icon = dataset.Icon,
            Categoria = dataset.Categoria,
            Configuration = DeserializeConfig(dataset.ConfigurationJson),
            Attivo = dataset.Attivo,
            CreatedAt = dataset.CreatedAt,
            UpdatedAt = dataset.UpdatedAt
        };
    }

    /// <summary>
    /// Crea un nuovo virtual dataset
    /// </summary>
    [HttpPost]
    public async Task<ActionResult<VirtualDatasetDto>> Create([FromBody] CreateVirtualDatasetRequest request)
    {
        // Verifica nome univoco
        var exists = await _context.VirtualDatasets.AnyAsync(v => v.Nome == request.Nome);
        if (exists)
            return BadRequest($"Un dataset con nome '{request.Nome}' esiste già");

        var dataset = new VirtualDataset
        {
            Nome = request.Nome,
            DisplayName = request.DisplayName,
            Descrizione = request.Descrizione,
            Icon = request.Icon ?? "dataset",
            Categoria = request.Categoria ?? "Personalizzato",
            ConfigurationJson = SerializeConfig(request.Configuration),
            Attivo = true,
            CreatedAt = DateTime.UtcNow
        };

        _context.VirtualDatasets.Add(dataset);
        await _context.SaveChangesAsync();

        return CreatedAtAction(nameof(GetById), new { id = dataset.Id }, new VirtualDatasetDto
        {
            Id = dataset.Id,
            Nome = dataset.Nome,
            DisplayName = dataset.DisplayName,
            Descrizione = dataset.Descrizione,
            Icon = dataset.Icon,
            Categoria = dataset.Categoria,
            Configuration = request.Configuration,
            Attivo = dataset.Attivo,
            CreatedAt = dataset.CreatedAt
        });
    }

    /// <summary>
    /// Aggiorna un virtual dataset
    /// </summary>
    [HttpPut("{id}")]
    public async Task<ActionResult<VirtualDatasetDto>> Update(int id, [FromBody] UpdateVirtualDatasetRequest request)
    {
        var dataset = await _context.VirtualDatasets.FindAsync(id);
        if (dataset == null)
            return NotFound();

        // Verifica nome univoco se cambiato
        if (request.Nome != null && request.Nome != dataset.Nome)
        {
            var exists = await _context.VirtualDatasets.AnyAsync(v => v.Nome == request.Nome && v.Id != id);
            if (exists)
                return BadRequest($"Un dataset con nome '{request.Nome}' esiste già");
            dataset.Nome = request.Nome;
        }

        if (request.DisplayName != null)
            dataset.DisplayName = request.DisplayName;
        if (request.Descrizione != null)
            dataset.Descrizione = request.Descrizione;
        if (request.Icon != null)
            dataset.Icon = request.Icon;
        if (request.Categoria != null)
            dataset.Categoria = request.Categoria;
        if (request.Configuration != null)
            dataset.ConfigurationJson = SerializeConfig(request.Configuration);
        if (request.Attivo.HasValue)
            dataset.Attivo = request.Attivo.Value;

        dataset.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        return new VirtualDatasetDto
        {
            Id = dataset.Id,
            Nome = dataset.Nome,
            DisplayName = dataset.DisplayName,
            Descrizione = dataset.Descrizione,
            Icon = dataset.Icon,
            Categoria = dataset.Categoria,
            Configuration = DeserializeConfig(dataset.ConfigurationJson),
            Attivo = dataset.Attivo,
            CreatedAt = dataset.CreatedAt,
            UpdatedAt = dataset.UpdatedAt
        };
    }

    /// <summary>
    /// Elimina un virtual dataset
    /// </summary>
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        var dataset = await _context.VirtualDatasets.FindAsync(id);
        if (dataset == null)
            return NotFound();

        _context.VirtualDatasets.Remove(dataset);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    /// <summary>
    /// Duplica un virtual dataset
    /// </summary>
    [HttpPost("{id}/clone")]
    public async Task<ActionResult<VirtualDatasetDto>> Clone(int id)
    {
        var source = await _context.VirtualDatasets.FindAsync(id);
        if (source == null)
            return NotFound();

        // Genera nome univoco
        var baseName = $"{source.Nome}_copia";
        var newName = baseName;
        var counter = 1;
        while (await _context.VirtualDatasets.AnyAsync(v => v.Nome == newName))
        {
            newName = $"{baseName}_{counter++}";
        }

        var clone = new VirtualDataset
        {
            Nome = newName,
            DisplayName = $"{source.DisplayName} (Copia)",
            Descrizione = source.Descrizione,
            Icon = source.Icon,
            Categoria = source.Categoria,
            ConfigurationJson = source.ConfigurationJson,
            Attivo = true,
            CreatedAt = DateTime.UtcNow
        };

        _context.VirtualDatasets.Add(clone);
        await _context.SaveChangesAsync();

        return CreatedAtAction(nameof(GetById), new { id = clone.Id }, new VirtualDatasetDto
        {
            Id = clone.Id,
            Nome = clone.Nome,
            DisplayName = clone.DisplayName,
            Descrizione = clone.Descrizione,
            Icon = clone.Icon,
            Categoria = clone.Categoria,
            Configuration = DeserializeConfig(clone.ConfigurationJson),
            Attivo = clone.Attivo,
            CreatedAt = clone.CreatedAt
        });
    }

    /// <summary>
    /// Ottiene le categorie disponibili
    /// </summary>
    [HttpGet("categories")]
    public async Task<ActionResult<List<string>>> GetCategories()
    {
        var categories = await _context.VirtualDatasets
            .Where(v => v.Attivo)
            .Select(v => v.Categoria)
            .Distinct()
            .OrderBy(c => c)
            .ToListAsync();

        // Aggiungi categoria default se non presente
        if (!categories.Contains("Personalizzato"))
            categories.Insert(0, "Personalizzato");

        return categories;
    }

    /// <summary>
    /// Valida la configurazione di un virtual dataset
    /// </summary>
    [HttpPost("validate")]
    public ActionResult<ValidationResult> Validate([FromBody] VirtualDatasetConfiguration config)
    {
        var errors = new List<string>();
        var warnings = new List<string>();

        // Verifica che ci sia almeno una sorgente
        if (config.Sources.Count == 0)
        {
            errors.Add("È necessario almeno un dataset sorgente");
        }

        // Verifica che ci sia un dataset primario
        if (config.Sources.Count > 0 && !config.Sources.Any(s => s.IsPrimary))
        {
            errors.Add("È necessario definire un dataset primario");
        }

        // Verifica che gli alias siano univoci
        var duplicateAliases = config.Sources
            .GroupBy(s => s.Alias)
            .Where(g => g.Count() > 1)
            .Select(g => g.Key)
            .ToList();
        if (duplicateAliases.Count > 0)
        {
            errors.Add($"Alias duplicati: {string.Join(", ", duplicateAliases)}");
        }

        // Verifica le relazioni
        foreach (var rel in config.Relationships)
        {
            var fromSource = config.Sources.FirstOrDefault(s => s.Id == rel.FromSourceId);
            var toSource = config.Sources.FirstOrDefault(s => s.Id == rel.ToSourceId);

            if (fromSource == null)
                errors.Add($"Relazione '{rel.Label ?? rel.Id}': sorgente di partenza non trovata");
            if (toSource == null)
                errors.Add($"Relazione '{rel.Label ?? rel.Id}': sorgente di destinazione non trovata");
        }

        // Verifica i filtri
        foreach (var filter in config.Filters.Where(f => f.Enabled))
        {
            var source = config.Sources.FirstOrDefault(s => s.Id == filter.SourceId);
            if (source == null)
                errors.Add($"Filtro su campo '{filter.Field}': sorgente non trovata");
        }

        // Warning per configurazioni potenzialmente problematiche
        if (config.Sources.Count > 1 && config.Relationships.Count == 0)
        {
            warnings.Add("Sono presenti più sorgenti ma nessuna relazione è definita. I dati saranno combinati come prodotto cartesiano.");
        }

        if (config.OutputFields.Count == 0)
        {
            warnings.Add("Nessun campo di output selezionato. Verranno inclusi tutti i campi disponibili.");
        }

        return new ValidationResult
        {
            IsValid = errors.Count == 0,
            Errors = errors,
            Warnings = warnings
        };
    }

    /// <summary>
    /// Ottiene lo schema risultante dal virtual dataset
    /// </summary>
    [HttpPost("schema")]
    public ActionResult<DataSchemaDto> GetVirtualSchema([FromBody] VirtualDatasetConfiguration config)
    {
        var fields = new List<DataFieldDto>();

        // Se ci sono output fields specifici, usa quelli
        if (config.OutputFields.Count > 0 && config.OutputFields.Any(f => f.Included))
        {
            foreach (var outputField in config.OutputFields.Where(f => f.Included).OrderBy(f => f.Order))
            {
                var source = config.Sources.FirstOrDefault(s => s.Id == outputField.SourceId);
                if (source == null) continue;

                fields.Add(new DataFieldDto
                {
                    Name = outputField.Alias ?? $"{source.Alias}.{outputField.FieldName}",
                    Label = outputField.Label ?? outputField.FieldName,
                    Type = "string", // TODO: determinare il tipo dal dataset sorgente
                    Group = outputField.Group ?? source.Alias
                });
            }
        }
        else
        {
            // Altrimenti, includi tutti i campi da tutte le sorgenti
            foreach (var source in config.Sources)
            {
                var sourceSchema = GetBaseDatasetSchema(source.DatasetId);
                if (sourceSchema == null) continue;

                foreach (var field in sourceSchema.Fields)
                {
                    fields.Add(new DataFieldDto
                    {
                        Name = $"{source.Alias}.{field.Name}",
                        Label = $"{source.Alias}: {field.Label}",
                        Type = field.Type,
                        Group = source.Alias
                    });
                }
            }
        }

        return new DataSchemaDto
        {
            EntityType = "Virtual Dataset",
            DatasetId = "virtual",
            Fields = fields,
            ChildCollections = new List<DataCollectionDto>()
        };
    }

    private static VirtualDatasetConfiguration DeserializeConfig(string json)
    {
        try
        {
            return JsonSerializer.Deserialize<VirtualDatasetConfiguration>(json, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            }) ?? new VirtualDatasetConfiguration();
        }
        catch
        {
            return new VirtualDatasetConfiguration();
        }
    }

    private static string SerializeConfig(VirtualDatasetConfiguration? config)
    {
        return JsonSerializer.Serialize(config ?? new VirtualDatasetConfiguration(), new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        });
    }

    private DataSchemaDto? GetBaseDatasetSchema(string datasetId)
    {
        // Riutilizza la logica di ReportsController per ottenere lo schema base
        // TODO: estrarre in un servizio condiviso
        return datasetId.ToLower() switch
        {
            "evento" => new DataSchemaDto
            {
                EntityType = "Evento",
                DatasetId = "evento",
                Fields = new List<DataFieldDto>
                {
                    new() { Name = "id", Type = "number", Label = "ID" },
                    new() { Name = "codice", Type = "string", Label = "Codice" },
                    new() { Name = "dataEvento", Type = "date", Label = "Data Evento" },
                    new() { Name = "descrizione", Type = "string", Label = "Descrizione" },
                    new() { Name = "stato", Type = "number", Label = "Stato" },
                    new() { Name = "numeroOspiti", Type = "number", Label = "Numero Ospiti" },
                    new() { Name = "costoTotale", Type = "currency", Label = "Costo Totale" },
                    new() { Name = "clienteId", Type = "number", Label = "ID Cliente" },
                    new() { Name = "locationId", Type = "number", Label = "ID Location" },
                },
                ChildCollections = new List<DataCollectionDto>()
            },
            "cliente" => new DataSchemaDto
            {
                EntityType = "Cliente",
                DatasetId = "cliente",
                Fields = new List<DataFieldDto>
                {
                    new() { Name = "id", Type = "number", Label = "ID" },
                    new() { Name = "ragioneSociale", Type = "string", Label = "Ragione Sociale" },
                    new() { Name = "indirizzo", Type = "string", Label = "Indirizzo" },
                    new() { Name = "citta", Type = "string", Label = "Città" },
                    new() { Name = "telefono", Type = "string", Label = "Telefono" },
                    new() { Name = "email", Type = "string", Label = "Email" },
                    new() { Name = "partitaIva", Type = "string", Label = "Partita IVA" },
                },
                ChildCollections = new List<DataCollectionDto>()
            },
            "location" => new DataSchemaDto
            {
                EntityType = "Location",
                DatasetId = "location",
                Fields = new List<DataFieldDto>
                {
                    new() { Name = "id", Type = "number", Label = "ID" },
                    new() { Name = "nome", Type = "string", Label = "Nome" },
                    new() { Name = "indirizzo", Type = "string", Label = "Indirizzo" },
                    new() { Name = "citta", Type = "string", Label = "Città" },
                    new() { Name = "distanzaKm", Type = "number", Label = "Distanza (km)" },
                },
                ChildCollections = new List<DataCollectionDto>()
            },
            "articolo" => new DataSchemaDto
            {
                EntityType = "Articolo",
                DatasetId = "articolo",
                Fields = new List<DataFieldDto>
                {
                    new() { Name = "id", Type = "number", Label = "ID" },
                    new() { Name = "codice", Type = "string", Label = "Codice" },
                    new() { Name = "descrizione", Type = "string", Label = "Descrizione" },
                    new() { Name = "qtaDisponibile", Type = "number", Label = "Qtà Disponibile" },
                    new() { Name = "categoriaId", Type = "number", Label = "ID Categoria" },
                },
                ChildCollections = new List<DataCollectionDto>()
            },
            "risorsa" => new DataSchemaDto
            {
                EntityType = "Risorsa",
                DatasetId = "risorsa",
                Fields = new List<DataFieldDto>
                {
                    new() { Name = "id", Type = "number", Label = "ID" },
                    new() { Name = "nome", Type = "string", Label = "Nome" },
                    new() { Name = "cognome", Type = "string", Label = "Cognome" },
                    new() { Name = "telefono", Type = "string", Label = "Telefono" },
                    new() { Name = "tipoRisorsaId", Type = "number", Label = "ID Tipo Risorsa" },
                },
                ChildCollections = new List<DataCollectionDto>()
            },
            _ => null
        };
    }
}

// DTOs
public class VirtualDatasetDto
{
    public int Id { get; set; }
    public string Nome { get; set; } = string.Empty;
    public string DisplayName { get; set; } = string.Empty;
    public string? Descrizione { get; set; }
    public string Icon { get; set; } = "dataset";
    public string Categoria { get; set; } = "Personalizzato";
    public VirtualDatasetConfiguration? Configuration { get; set; }
    public bool Attivo { get; set; }
    public DateTime? CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
}

public class CreateVirtualDatasetRequest
{
    public string Nome { get; set; } = string.Empty;
    public string DisplayName { get; set; } = string.Empty;
    public string? Descrizione { get; set; }
    public string? Icon { get; set; }
    public string? Categoria { get; set; }
    public VirtualDatasetConfiguration? Configuration { get; set; }
}

public class UpdateVirtualDatasetRequest
{
    public string? Nome { get; set; }
    public string? DisplayName { get; set; }
    public string? Descrizione { get; set; }
    public string? Icon { get; set; }
    public string? Categoria { get; set; }
    public VirtualDatasetConfiguration? Configuration { get; set; }
    public bool? Attivo { get; set; }
}

public class ValidationResult
{
    public bool IsValid { get; set; }
    public List<string> Errors { get; set; } = new();
    public List<string> Warnings { get; set; } = new();
}
