using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Data.SqlClient;
using SIGCM2.Domain.Exceptions;

namespace SIGCM2.Api.Filters;

public sealed class ExceptionFilter : IExceptionFilter
{
    private readonly ILogger<ExceptionFilter> _logger;

    public ExceptionFilter(ILogger<ExceptionFilter> logger)
    {
        _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        switch (context.Exception)
        {
            case UsuarioNotFoundException usuarioNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "usuario_not_found",
                    message = usuarioNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case LastAdminLockoutException:
                context.Result = new ObjectResult(new Microsoft.AspNetCore.Mvc.ProblemDetails
                {
                    Type = "about:blank",
                    Title = "last-admin-lockout",
                    Status = 400,
                    Detail = "No se puede desactivar o cambiar el rol del último administrador activo."
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            case CannotSelfResetException:
                context.Result = new ObjectResult(new
                {
                    error = "cannot-self-reset",
                    message = "Un administrador no puede resetear su propia contraseña. Use el endpoint de cambio de contraseña propio."
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            case InvalidOldPasswordException:
                context.Result = new ObjectResult(new
                {
                    error = "invalid-old-password",
                    message = "La contraseña actual es incorrecta."
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            case UsernameAlreadyExistsException usernameEx:
                context.Result = new ObjectResult(new
                {
                    error = "username_taken",
                    message = usernameEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case SqlException sqlEx when sqlEx.Number == 2627:
                // Safety net: UQ constraint violation from a race condition
                context.Result = new ObjectResult(new
                {
                    error = "username_taken",
                    message = "El nombre de usuario ya está en uso."
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case InvalidCredentialsException:
                context.Result = new ObjectResult(new { error = "Credenciales inválidas" })
                {
                    StatusCode = StatusCodes.Status401Unauthorized
                };
                context.ExceptionHandled = true;
                break;

            case TokenReuseDetectedException reuseEx:
                // Log with detail on the backend but return generic 401 to client
                _logger.LogWarning("Token reuse detected — possible session compromise: {Message}", reuseEx.Message);
                context.Result = new ObjectResult(new { error = "Token inválido" })
                {
                    StatusCode = StatusCodes.Status401Unauthorized
                };
                context.ExceptionHandled = true;
                break;

            case InvalidRefreshTokenException:
                // Generic 401 — do NOT reveal if token was expired, not found, or mismatched
                context.Result = new ObjectResult(new { error = "Token inválido" })
                {
                    StatusCode = StatusCodes.Status401Unauthorized
                };
                context.ExceptionHandled = true;
                break;

            case RolNotFoundException rolNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "rol_not_found",
                    message = rolNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case PermisoNotFoundException permisoNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "permiso_not_found",
                    message = permisoNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case RolAlreadyExistsException rolExistsEx:
                context.Result = new ObjectResult(new
                {
                    error = "rol_already_exists",
                    message = rolExistsEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case RolInUseException rolInUseEx:
                context.Result = new ObjectResult(new
                {
                    error = "rol_in_use",
                    message = rolInUseEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            // CAT-001: Rubro exceptions
            case RubroNotFoundException rubroNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_not_found",
                    message = rubroNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case RubroNombreDuplicadoEnPadreException rubroDupEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_nombre_duplicado",
                    message = rubroDupEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case RubroTieneHijosActivosException rubroHijosEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_tiene_hijos_activos",
                    message = rubroHijosEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case RubroPadreInactivoException rubroPadreEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_padre_inactivo",
                    message = rubroPadreEx.Message
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            case RubroMaxDepthExceededException rubroDepthEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_max_depth_exceeded",
                    message = rubroDepthEx.Message
                })
                {
                    StatusCode = StatusCodes.Status422UnprocessableEntity
                };
                context.ExceptionHandled = true;
                break;

            case RubroCycleDetectedException rubroCycleEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_cycle_detected",
                    message = rubroCycleEx.Message
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            // CAT-002: Rubro Regla de Oro (rama vs hoja)
            case RubroPadreEsHojaConAvisosException rubroPadreHojaEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_padre_es_hoja_con_avisos",
                    message = rubroPadreHojaEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case RubroEsRamaConHijosActivosException rubroRamaHijosEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_es_rama_con_hijos_activos",
                    message = rubroRamaHijosEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            // ADM-001: Medio exceptions
            case MedioCodigoDuplicadoException medioCodDupEx:
                context.Result = new ObjectResult(new
                {
                    error = "medio_codigo_duplicado",
                    message = medioCodDupEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case MedioNotFoundException medioNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "medio_not_found",
                    message = medioNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case MedioInactivoException medioInactivoEx:
                context.Result = new ObjectResult(new
                {
                    error = "medio_inactivo",
                    message = medioInactivoEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            // ADM-001: Seccion exceptions
            case SeccionCodigoDuplicadoEnMedioException seccionCodDupEx:
                context.Result = new ObjectResult(new
                {
                    error = "seccion_codigo_duplicado_en_medio",
                    message = seccionCodDupEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case SeccionNotFoundException seccionNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "seccion_not_found",
                    message = seccionNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            // ADM-009: TipoDeIva fiscal exceptions
            case PorcentajeInmutableException:
                context.Result = new ObjectResult(new
                {
                    error = "inmutable_usar_nueva_version",
                    message = "El porcentaje de un TipoDeIva es inmutable. Creá una nueva versión vía POST /iva/{id}/nueva-version."
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case AlicuotaInmutableException:
                context.Result = new ObjectResult(new
                {
                    error = "inmutable_usar_nueva_version",
                    message = "La alícuota de IngresosBrutos es inmutable. Creá una nueva versión vía POST /iibb/{id}/nueva-version."
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case PredecesorYaCerradoException predecesorYaCerradoEx:
                context.Result = new ObjectResult(new
                {
                    error = "predecesora_ya_cerrada",
                    message = predecesorYaCerradoEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case DuplicateCodigoException duplicateCodigoEx:
                context.Result = new ObjectResult(new
                {
                    error = "duplicate_codigo",
                    message = duplicateCodigoEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case DuplicateProvinciaException duplicateProvinciaEx:
                context.Result = new ObjectResult(new
                {
                    error = "duplicate_provincia",
                    message = duplicateProvinciaEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case TipoDeIvaNotFoundException tipoDeIvaNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "tipo_iva_not_found",
                    message = tipoDeIvaNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case IngresosBrutosNotFoundException ingresosBrutosNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "ingresos_brutos_not_found",
                    message = ingresosBrutosNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            // PRD-001: ProductType exceptions
            case ProductTypeNotFoundException productTypeNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_type_not_found",
                    message = productTypeNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case ProductTypeNombreDuplicadoException productTypeDupEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_type_nombre_duplicado",
                    message = productTypeDupEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case ProductTypeEnUsoException productTypeEnUsoEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_type_en_uso",
                    message = productTypeEnUsoEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case ProductTypeFlagsIncoherentesException productTypeFlagsEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_type_flags_incoherentes",
                    message = productTypeFlagsEx.Message
                })
                {
                    StatusCode = StatusCodes.Status422UnprocessableEntity
                };
                context.ExceptionHandled = true;
                break;

            // PRD-002: Product exceptions
            case ProductNotFoundException productNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_not_found",
                    message = productNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case ProductNombreDuplicadoEnMedioTipoException productDupEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_nombre_duplicado",
                    message = productDupEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case ProductTipoFlagsIncoherentesException productFlagsEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_flags_incoherentes",
                    field = productFlagsEx.Field,
                    message = productFlagsEx.Message
                })
                {
                    StatusCode = StatusCodes.Status422UnprocessableEntity
                };
                context.ExceptionHandled = true;
                break;

            case ProductTypeInactivoException productTypeInactivoEx:
                context.Result = new ObjectResult(new
                {
                    error = "product_type_inactivo",
                    message = productTypeInactivoEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            case RubroInactivoException rubroInactivoEx:
                context.Result = new ObjectResult(new
                {
                    error = "rubro_inactivo",
                    message = rubroInactivoEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            // ADM-008: PuntoDeVenta exceptions
            case PuntoDeVentaNotFoundException puntoDeVentaNotFoundEx:
                context.Result = new ObjectResult(new
                {
                    error = "punto_de_venta_not_found",
                    message = puntoDeVentaNotFoundEx.Message
                })
                {
                    StatusCode = StatusCodes.Status404NotFound
                };
                context.ExceptionHandled = true;
                break;

            case NumeroAFIPDuplicadoException numeroAFIPDupEx:
                context.Result = new ObjectResult(new
                {
                    error = "numero_afip_duplicado",
                    message = numeroAFIPDupEx.Message
                })
                {
                    StatusCode = StatusCodes.Status409Conflict
                };
                context.ExceptionHandled = true;
                break;

            // UDT-009: permiso override validation errors
            case InvalidPermisoCodesException ipce:
                context.Result = new ObjectResult(new Microsoft.AspNetCore.Mvc.ProblemDetails
                {
                    Type = "about:blank",
                    Title = "invalid-permiso-codes",
                    Status = 400,
                    Extensions = { ["invalidCodes"] = ipce.InvalidCodes }
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            case GrantDenyOverlapException gdoe:
                context.Result = new ObjectResult(new Microsoft.AspNetCore.Mvc.ProblemDetails
                {
                    Type = "about:blank",
                    Title = "grant-deny-overlap",
                    Status = 400,
                    Extensions = { ["overlap"] = gdoe.Overlap }
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            // ADM-009: vigencia_desde_invalida — domain throws ArgumentException for invalid vigencia range
            case ArgumentException argEx when argEx.Message.Contains("vigencia_desde_invalida") ||
                                              argEx.ParamName == "vigenciaDesde" ||
                                              argEx.Message.Contains("debe ser posterior"):
                context.Result = new ObjectResult(new
                {
                    error = "vigencia_desde_invalida",
                    message = argEx.Message
                })
                {
                    StatusCode = StatusCodes.Status400BadRequest
                };
                context.ExceptionHandled = true;
                break;

            case ValidationException validationEx:
                var errors = validationEx.Errors
                    .GroupBy(e => e.PropertyName)
                    .ToDictionary(
                        g => g.Key,
                        g => g.Select(e => e.ErrorMessage).ToArray());

                context.Result = new BadRequestObjectResult(new { errors });
                context.ExceptionHandled = true;
                break;

            default:
                _logger.LogError(context.Exception, "Unhandled exception");
                context.Result = new ObjectResult(new { error = "Internal server error" })
                {
                    StatusCode = StatusCodes.Status500InternalServerError
                };
                context.ExceptionHandled = true;
                break;
        }
    }
}
