// src/Elecciones.Api/Controllers/AdminController.cs
using Elecciones.Database;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Elecciones.Core.DTOs.ApiRequests;
using Elecciones.Database.Entities;
using Microsoft.AspNetCore.Authorization;
using Elecciones.Core.Enums;
using Elecciones.Infrastructure.Services;

namespace Elecciones.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class AdminController : ControllerBase
{
  private readonly EleccionesDbContext _dbContext;
  private readonly ILogger<AdminController> _logger;
  private readonly LoggingSwitchService _loggingSwitchService;

  public AdminController(EleccionesDbContext dbContext, ILogger<AdminController> logger, LoggingSwitchService loggingSwitchService)
  {
    _dbContext = dbContext;
    _logger = logger;
    _loggingSwitchService = loggingSwitchService;
  }

  // Endpoint para obtener todas las agrupaciones para el panel de admin
  [HttpGet("agrupaciones")]
  public async Task<IActionResult> GetAgrupaciones()
  {
    var agrupaciones = await _dbContext.AgrupacionesPoliticas
        .AsNoTracking()
        .OrderBy(a => a.Nombre)
        .ToListAsync();

    return Ok(agrupaciones);
  }

  [HttpPut("agrupaciones/{id}")]
  public async Task<IActionResult> UpdateAgrupacion(string id, [FromBody] UpdateAgrupacionDto agrupacionDto)
  {
    var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(id);

    if (agrupacion == null)
    {
      return NotFound(new { message = $"No se encontró la agrupación con ID {id}" });
    }

    agrupacion.NombreCorto = agrupacionDto.NombreCorto;
    agrupacion.Color = agrupacionDto.Color;

    await _dbContext.SaveChangesAsync();
    _logger.LogInformation("Se actualizó la agrupación: {Id}", id);

    return NoContent();
  }

  [HttpPut("agrupaciones/orden-diputados")]
  public async Task<IActionResult> UpdateDiputadosOrden([FromBody] List<string> idsAgrupacionesOrdenadas)
  {
    // Reseteamos solo el orden de diputados
    await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenDiputados, (int?)null));

    for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++)
    {
      var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]);
      if (agrupacion != null) agrupacion.OrdenDiputados = i + 1;
    }
    await _dbContext.SaveChangesAsync();
    return Ok();
  }

  [HttpPut("agrupaciones/orden-senadores")]
  public async Task<IActionResult> UpdateSenadoresOrden([FromBody] List<string> idsAgrupacionesOrdenadas)
  {
    // Reseteamos solo el orden de senadores
    await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenSenadores, (int?)null));

    for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++)
    {
      var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]);
      if (agrupacion != null) agrupacion.OrdenSenadores = i + 1;
    }
    await _dbContext.SaveChangesAsync();
    return Ok();
  }

  // --- ENDPOINTS PARA NACIONALES ---

  [HttpPut("agrupaciones/orden-diputados-nacionales")]
  public async Task<IActionResult> UpdateDiputadosNacionalesOrden([FromBody] List<string> idsAgrupacionesOrdenadas)
  {
    await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenDiputadosNacionales, (int?)null));

    for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++)
    {
      var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]);
      if (agrupacion != null) agrupacion.OrdenDiputadosNacionales = i + 1;
    }
    await _dbContext.SaveChangesAsync();
    return Ok();
  }

  [HttpPut("agrupaciones/orden-senadores-nacionales")]
  public async Task<IActionResult> UpdateSenadoresNacionalesOrden([FromBody] List<string> idsAgrupacionesOrdenadas)
  {
    await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenSenadoresNacionales, (int?)null));

    for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++)
    {
      var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]);
      if (agrupacion != null) agrupacion.OrdenSenadoresNacionales = i + 1;
    }
    await _dbContext.SaveChangesAsync();
    return Ok();
  }

  // LEER todas las configuraciones
  [HttpGet("configuracion")]
  public async Task<IActionResult> GetConfiguracion()
  {
    var configs = await _dbContext.Configuraciones.AsNoTracking().ToListAsync();
    // Devolvemos un diccionario para que sea fácil de consumir en el frontend
    return Ok(configs.ToDictionary(c => c.Clave, c => c.Valor));
  }

  // GUARDAR un conjunto de configuraciones
  [HttpPut("configuracion")]
  public async Task<IActionResult> UpdateConfiguracion([FromBody] Dictionary<string, string> nuevasConfiguraciones)
  {
    foreach (var kvp in nuevasConfiguraciones)
    {
      var config = await _dbContext.Configuraciones.FindAsync(kvp.Key);
      if (config == null)
      {
        // Si no existe, la creamos
        _dbContext.Configuraciones.Add(new Configuracion { Clave = kvp.Key, Valor = kvp.Value });
      }
      else
      {
        // Si existe, la actualizamos
        config.Valor = kvp.Value;
      }
    }
    await _dbContext.SaveChangesAsync();
    return NoContent();
  }

  // LEER: Obtener todas las bancadas para una cámara, con su partido y ocupante actual
  [HttpGet("bancadas/{camara}")]
  public async Task<IActionResult> GetBancadas(TipoCamara camara, [FromQuery] int eleccionId)
  {
    // 3. La lógica interna se mantiene igual, ya que filtra por ambos parámetros.
    var bancadas = await _dbContext.Bancadas
        .AsNoTracking()
        .Include(b => b.AgrupacionPolitica)
        .Include(b => b.Ocupante)
        .Where(b => b.EleccionId == eleccionId && b.Camara == camara)
        .OrderBy(b => b.NumeroBanca)
        .ToListAsync();

    return Ok(bancadas);
  }

  // ACTUALIZAR: Asignar un partido y/o un ocupante a una bancada específica
  [HttpPut("bancadas/{bancadaId}")]
  public async Task<IActionResult> UpdateBancada(int bancadaId, [FromBody] UpdateBancadaDto dto)
  {
    var bancada = await _dbContext.Bancadas
        .Include(b => b.Ocupante)
        .FirstOrDefaultAsync(b => b.Id == bancadaId);

    if (bancada == null)
    {
      return NotFound(new { message = "La bancada especificada no existe." });
    }

    // 1. Actualizar la agrupación de la bancada
    bancada.AgrupacionPoliticaId = dto.AgrupacionPoliticaId;

    // 2. Lógica de "Upsert" (Update/Insert/Delete) para el ocupante
    if (string.IsNullOrEmpty(dto.NombreOcupante))
    {
      // Si no se envía nombre, se elimina el ocupante existente
      if (bancada.Ocupante != null)
      {
        _dbContext.OcupantesBancas.Remove(bancada.Ocupante);
      }
    }
    else
    {
      // Si se envía un nombre, se crea o actualiza el ocupante
      if (bancada.Ocupante == null)
      {
        bancada.Ocupante = new OcupanteBanca(); // Crea uno nuevo si no existía
      }
      bancada.Ocupante.NombreOcupante = dto.NombreOcupante;
      bancada.Ocupante.FotoUrl = dto.FotoUrl;
      bancada.Ocupante.Periodo = dto.Periodo;
    }

    await _dbContext.SaveChangesAsync();
    _logger.LogInformation("Se actualizó la bancada con ID: {BancadaId}", bancadaId);

    return NoContent();
  }

  [HttpGet("catalogos/provincias")]
  public async Task<IActionResult> GetProvinciasForAdmin()
  {
    var provincias = await _dbContext.AmbitosGeograficos
        .AsNoTracking()
        .Where(a => a.NivelId == 10) // Nivel 10 = Provincia
        .OrderBy(a => a.Nombre)
        .Select(a => new { Id = a.Id.ToString(), Nombre = a.Nombre })
        .ToListAsync();
    return Ok(provincias);
  }

  // --- ENDPOINTS MODIFICADOS ---

  [HttpGet("logos")]
  public async Task<IActionResult> GetLogos([FromQuery] int eleccionId)
  {
    // Añadimos el filtro por EleccionId
    return Ok(await _dbContext.LogosAgrupacionesCategorias
        .AsNoTracking()
        .Where(l => l.EleccionId == eleccionId)
        .ToListAsync());
  }

  [HttpGet("candidatos")]
  public async Task<IActionResult> GetCandidatos([FromQuery] int eleccionId)
  {
    // Añadimos el filtro por EleccionId
    var candidatos = await _dbContext.CandidatosOverrides
        .AsNoTracking()
        .Where(c => c.EleccionId == eleccionId)
        .ToListAsync();
    return Ok(candidatos);
  }

  [HttpPut("logos")]
  public async Task<IActionResult> UpdateLogos([FromBody] List<LogoAgrupacionCategoria> logos)
  {
    foreach (var logo in logos)
    {
      // Buscamos un registro existente que coincida exactamente
      var logoExistente = await _dbContext.LogosAgrupacionesCategorias
          .FirstOrDefaultAsync(l =>
              l.EleccionId == logo.EleccionId &&
              l.AgrupacionPoliticaId == logo.AgrupacionPoliticaId &&
              l.CategoriaId == logo.CategoriaId &&
              l.AmbitoGeograficoId == logo.AmbitoGeograficoId);

      if (logoExistente != null)
      {
        // Si existe, lo actualizamos
        if (string.IsNullOrEmpty(logo.LogoUrl))
        {
          // Si la URL nueva es vacía, eliminamos el override
          _dbContext.LogosAgrupacionesCategorias.Remove(logoExistente);
        }
        else
        {
          logoExistente.LogoUrl = logo.LogoUrl;
        }
      }
      else if (!string.IsNullOrEmpty(logo.LogoUrl))
      {
        // Si no existe y la URL no es vacía, lo creamos
        _dbContext.LogosAgrupacionesCategorias.Add(logo);
      }
    }
    await _dbContext.SaveChangesAsync();
    return NoContent();
  }

  [HttpGet("catalogos/municipios")]
  public async Task<IActionResult> GetMunicipiosForAdmin()
  {
    var municipios = await _dbContext.AmbitosGeograficos
        .AsNoTracking()
        .Where(a => a.NivelId == 30) // Nivel 30 = Municipio
        .OrderBy(a => a.Nombre)
        .Select(a => new
        {
          // Devolvemos el ID de la base de datos como un string,
          // que es lo que el componente Select espera.
          Id = a.Id.ToString(),
          Nombre = a.Nombre
        })
        .ToListAsync();

    return Ok(municipios);
  }

  /// <summary>
  /// Guarda (actualiza o crea) una lista de overrides de candidatos.
  /// </summary>
  [HttpPut("candidatos")]
  public async Task<IActionResult> UpdateCandidatos([FromBody] List<UpdateCandidatoDto> candidatos)
  {
    foreach (var candidatoDto in candidatos)
    {
      var candidatoExistente = await _dbContext.CandidatosOverrides
          .FirstOrDefaultAsync(c =>
              c.AgrupacionPoliticaId == candidatoDto.AgrupacionPoliticaId &&
              c.CategoriaId == candidatoDto.CategoriaId &&
              c.AmbitoGeograficoId == candidatoDto.AmbitoGeograficoId);

      if (candidatoExistente != null)
      {
        // El registro ya existe
        if (string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato))
        {
          // El usuario envió un nombre vacío -> Eliminar el registro
          _dbContext.CandidatosOverrides.Remove(candidatoExistente);
        }
        else
        {
          // El usuario envió un nombre válido -> Actualizar
          candidatoExistente.NombreCandidato = candidatoDto.NombreCandidato;
        }
      }
      else
      {
        // El registro no existe
        if (!string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato))
        {
          // El usuario envió un nombre válido -> Crear nuevo registro
          _dbContext.CandidatosOverrides.Add(new CandidatoOverride
          {
            AgrupacionPoliticaId = candidatoDto.AgrupacionPoliticaId,
            CategoriaId = candidatoDto.CategoriaId,
            AmbitoGeograficoId = candidatoDto.AmbitoGeograficoId,
            NombreCandidato = candidatoDto.NombreCandidato
          });
        }
        // Si no existe y el nombre está vacío, no hacemos nada.
      }
    }

    await _dbContext.SaveChangesAsync();
    return NoContent();
  }

  /// <summary>
  /// Actualiza el nivel mínimo de logging en tiempo real y guarda la configuración en la BD.
  /// </summary>
  /// <param name="request">Un objeto que contiene el nuevo nivel de logging.</param>
  [HttpPut("logging-level")]
  public async Task<IActionResult> UpdateLoggingLevel([FromBody] UpdateLoggingLevelRequest request)
  {
    if (string.IsNullOrWhiteSpace(request.Level))
    {
      return BadRequest("El nivel de logging no puede estar vacío.");
    }

    // 1. Intentamos actualizar el interruptor de Serilog en memoria.
    bool success = _loggingSwitchService.SetLoggingLevel(request.Level);

    if (!success)
    {
      return BadRequest($"El nivel de logging '{request.Level}' no es válido. Los valores posibles son: Verbose, Debug, Information, Warning, Error, Fatal.");
    }

    // 2. Si el cambio fue exitoso, guardamos el nuevo valor en la base de datos.
    var config = await _dbContext.Configuraciones.FindAsync("Logging_Level");
    if (config == null)
    {
      _dbContext.Configuraciones.Add(new Configuracion { Clave = "Logging_Level", Valor = request.Level });
    }
    else
    {
      config.Valor = request.Level;
    }

    await _dbContext.SaveChangesAsync();

    _logger.LogWarning("El nivel de logging ha sido cambiado a: {Level}", request.Level);
    return Ok(new { message = $"Nivel de logging actualizado a '{request.Level}'." });
  }

  // LEER todas las bancas previas para una elección
  [HttpGet("bancas-previas/{eleccionId}")]
  public async Task<IActionResult> GetBancasPrevias(int eleccionId)
  {
    var bancas = await _dbContext.BancasPrevias
        .AsNoTracking()
        .Where(b => b.EleccionId == eleccionId)
        .Include(b => b.AgrupacionPolitica)
        .ToListAsync();
    return Ok(bancas);
  }

  // GUARDAR (Upsert) una lista de bancas previas
  [HttpPut("bancas-previas/{eleccionId}")]
  public async Task<IActionResult> UpdateBancasPrevias(int eleccionId, [FromBody] List<BancaPrevia> bancas)
  {
    // Borramos los registros existentes para esta elección para simplificar la lógica
    await _dbContext.BancasPrevias.Where(b => b.EleccionId == eleccionId).ExecuteDeleteAsync();

    // Añadimos los nuevos registros que tienen al menos una banca
    foreach (var banca in bancas.Where(b => b.Cantidad > 0))
    {
      _dbContext.BancasPrevias.Add(new BancaPrevia
      {
        EleccionId = eleccionId,
        Camara = banca.Camara,
        AgrupacionPoliticaId = banca.AgrupacionPoliticaId,
        Cantidad = banca.Cantidad
      });
    }

    await _dbContext.SaveChangesAsync();
    _logger.LogInformation("Se actualizaron las bancas previas para la EleccionId: {EleccionId}", eleccionId);
    return NoContent();
  }
}