package com.sc.sicanet.migracion_sicanet.service;

import com.sc.sicanet.migracion_sicanet.DTO.*;
import com.sc.sicanet.migracion_sicanet.entity.*;
import com.sc.sicanet.migracion_sicanet.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Component
public class Validaciones implements Validator {
    @Autowired
    private PldPaisesRepository pldPaisesRepository;
    @Autowired
    private CatGeoEntidadesRepository catGeoEntidadesRepository;
    @Autowired
    private CatGeoLocalidadesRepository catGeoLocalidadesRepository;
    @Autowired
    private CatGeoMunicipiosRepository catGeoMunicipiosRepository;
    @Autowired
    private CatTipoViviendaRepository catTipoViviendaRepository;
    @Autowired
    private CatTipoCasaRepository catTipoCasaRepository;
    @Autowired
    private CatTipoViveZonaRepository catTipoViveZonaRepository;
    @Autowired
    private CatTipoAsentamientoRepository catTipoAsentamientoRepository;
    @Autowired
    private CatTipoVialidadRepository catTipoVialidadRepository;
    @Autowired
    private CatTipoOcupacionRepository catTipoOcupacionRepository;
    @Autowired
    private PldGiroActividadRepository pldGiroActividadRepository;
    @Autowired
    private CatEstadoCivilRepository catEstadoCivilRepository;
    @Autowired
    private CatDestinoRecursoRepository catDestinoRecursoRepository;
    @Autowired
    private CatOrigenRecursoRepository catOrigenRecursoRepository;
    @Autowired
    private CatPeriodosRepository catPeriodosRepository;
    @Autowired
    private CatFormaPagoRepository catFormaPagoRepository;
    @Autowired
    private PerfilTransaccionalMontosRepository perfilTransaccionalMontosRepository;
    @Autowired
    private CodigosPostalesRepository codigosPostalesRepository;

    @Override
    public boolean supports(Class<?> clazz) {
        return PersonaDTO.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        PersonaDTO personaDTO = (PersonaDTO) target;
        validarInformacionPersona(personaDTO, errors);
        validarInformacionDomicilio(personaDTO.getDomicilio_personal(), errors, "domicilio_personal", "Domicilio Personal");
        validarInformacionLaboral(personaDTO.getLaboral(), errors);
        validarSocioeconomicosOrigen(personaDTO.getSocioeconomicos(), errors);
        validarSocioeconomicosDestino(personaDTO.getSocioeconomicos(), errors);
        validarPerfilTransaccional(personaDTO.getPerfil_transaccional(), errors);
    }

    private void validarInformacionPersona(PersonaDTO personaDTO, Errors errors){
        validarEntidadNacimiento(personaDTO.getEntidad_nacimiento(), errors);
        validarPersonaRfcOrCurp(personaDTO.getRfc(), personaDTO.getCurp(), personaDTO.getTipo_persona(), errors, "");
        if (personaDTO.getTipo_persona().equals("F")) {
            validarFechaNacimiento(personaDTO.getFecha_nacimiento(), errors);
            validarPrefijoEstadoCivil(personaDTO.getEstado_civil(), errors);
            validarSexo(personaDTO.getSexo(), errors);
        } else {
            validarPersonaRfcOrCurp(personaDTO.getRepresentante_legal().getRfc(), personaDTO.getRepresentante_legal().getCurp(),
                    personaDTO.getRepresentante_legal().getTipo_persona(), errors, "representante_legal.");
            validarFechaNacimiento(personaDTO.getRepresentante_legal().getFecha_nacimiento(), errors);
            validarSexo(personaDTO.getRepresentante_legal().getSexo(), errors);
            validarPrefijoEstadoCivil(personaDTO.getRepresentante_legal().getEstado_civil(), errors);
            validarInformacionDomicilio(personaDTO.getRepresentante_legal().getDomicilio_personal(), errors, "domicilio_personal", "Domicilio Personal");
        }
    }

    private void validarInformacionDomicilio(DomicilioDTO domicilioDTO, Errors errors, String nodo, String nodoMensaje){
        Optional<PldPaises> pldPaises = paises(domicilioDTO.getPais());
        if(pldPaises.isEmpty()) errors.rejectValue(nodo + ".pais", "400", nodoMensaje + ": País No Válido.");
        Optional<CatGeoEntidades> catGeoEntidades = entidades(domicilioDTO.getEntidad());
        if(catGeoEntidades.isEmpty()){
            errors.rejectValue(nodo + ".entidad", "400", nodoMensaje + ": Entidad No Válida.");
        } else {
            Optional<CatGeoMunicipios> catGeoMunicipios = municipios(domicilioDTO.getMunicipio(), catGeoEntidades.get().getPkCatEntidad());
            if(catGeoMunicipios.isEmpty()){
                errors.rejectValue(nodo + ".municipio", "400", nodoMensaje + ": Municipio No Válido");
            } else {
                Optional<CatGeoLocalidades> catGeoLocalidades = localidades(domicilioDTO.getLocalidad(), catGeoMunicipios.get().getPkCatMunicipio(),
                        catGeoEntidades.get().getPkCatEntidad());
                Optional<CodigosPostales> codigosPostales = codigosPostales(domicilioDTO.getCp(), catGeoEntidades.get().getPkCatEntidad(),
                        catGeoMunicipios.get().getPkCatMunicipio());
                if(catGeoEntidades.isEmpty()) errors.rejectValue(nodo + ".localidad", "400", nodoMensaje + ": Localidad No Válida");
                if(codigosPostales.isEmpty()) errors.rejectValue(nodo + ".cp","400", nodoMensaje + ": Código Postal No Válido");
            }
        }
        Optional<CatTipoVivienda> catTipoVivienda = catTipoVivienda(domicilioDTO.getTipo_vivienda());
        if(catTipoVivienda.isEmpty()) errors.rejectValue(nodo + ".tipo_vivienda", "400", nodoMensaje + ": Tipo Vivienda No Válido");

        Optional<CatTipoCasa> catTipoCasa = catTipoCasa(domicilioDTO.getTipo_casa());
        if(catTipoCasa.isEmpty()) errors.rejectValue(nodo + ".tipo_casa", "400", nodoMensaje + ": Tipo Casa No Válido");

        Optional<CatTipoViveZona> catTipoViveZona = catTipoViveZona(domicilioDTO.getZona_residencia());
        if(catTipoViveZona.isEmpty()) errors.rejectValue(nodo + ".zona_residencia", "400", nodoMensaje + ": Zona Residencia No Válida");

        Optional<CatTipoAsentamiento> catTipoAsentamiento = catTipoAsentamiento(domicilioDTO.getTipo_asentamiento());
        if(catTipoAsentamiento.isEmpty())
            errors.rejectValue(nodo + ".tipo_asentamiento", "400", nodoMensaje + ": Tipo Asentamiento No Válido");

        Optional<CatTipoVialidad> catTipoVialidad = catTipoVialidad(domicilioDTO.getTipo_vialidad());
        if(catTipoVialidad.isEmpty()) errors.rejectValue(nodo + ".tipo_vialidad", "400", nodoMensaje + ": Tipo Vialidad No Válido");

        if(domicilioDTO.getUuid() != null && !domicilioDTO.getUuid().isEmpty() &&
                !domicilioDTO.getUuid().matches("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")){
            errors.rejectValue(nodo + ".uuid", "400", "La clave UUID No Tiene Un Formato Válido.");
        }
    }

    private void validarInformacionLaboral(LaboralDTO laboralDTO, Errors errors){
        String nodo_laboral = "laboral";
        Optional<CatTipoOcupacion> catTipoOcupacion = catTipoOcupacionRepository.findByDescripcion(laboralDTO.getTipo_ocupacion());
        if(catTipoOcupacion.isEmpty()) errors.rejectValue(nodo_laboral + ".tipo_ocupacion", "400", "Tipo De Ocupación No Válida");
        Optional<PldGiroActividad> pldGiroActividad = pldGiroActividadRepository.findByPkGiroActividad(laboralDTO.getActividad_economica());
        if(pldGiroActividad.isEmpty()) errors.rejectValue(nodo_laboral + ".actividad_economica", "400", "Actividad Económica No Válida");
        validarInformacionDomicilio(laboralDTO.getDomicilio_laboral(), errors, nodo_laboral + ".domicilio_laboral", "Domicilio Laboral");
    }

    private void validarSocioeconomicosDestino(SocioeconomicosDTO pldCatPersonasRecursoDestinoDTO, Errors errors){
        String nodo = "socioeconomicos";
        validarDestinoRecurso(pldCatPersonasRecursoDestinoDTO.getDestino_recurso(), errors, nodo);
    }

    private void validarSocioeconomicosOrigen(SocioeconomicosDTO pldCatPersonasRecursoOrigenDTO, Errors errors){
        String nodo = "socioeconomicos";
        validarOrigenRecurso(pldCatPersonasRecursoOrigenDTO.getOrigen_recurso(), errors, nodo);
    }

    private void validarPerfilTransaccional(PerfilTransaccionalDTO perfilTransaccionalDTO, Errors errors){
        String nodo = "perfil_transaccional";
        validarFormaDePago(perfilTransaccionalDTO.getForma_pago(), errors, nodo);
        validarPeriodos(perfilTransaccionalDTO.getPeriodicidad(), errors, nodo);
        validarDisponibilidadMensual(perfilTransaccionalDTO.getDisponibilidad_mensual(), errors, nodo);
    }

    private void validarOrigenRecurso(String descripcion, Errors errors, String nodo){
        Optional<CatOrigenRecurso> catOrigenRecurso = catOrigenRecurso(descripcion);
        if (catOrigenRecurso.isEmpty()) errors.rejectValue(nodo + ".origen_recurso","400","El Origen De Recurso No Es Válido");
    }

    private void validarDestinoRecurso(String destino, Errors errors, String nodo){
        Optional<CatDestinos> catDestinos = catDestinos(destino);
        if (catDestinos.isEmpty())errors.rejectValue(nodo + ".destino_recurso","400","El Destino De Recurso No Es Válido");
    }

    private void validarFormaDePago(List<String> forma, Errors errors, String nodo){
        for (String _forma : forma) {
            Optional<CatFormaPago> catFormaPago = catFormaPago(_forma.trim());
            if (catFormaPago.isEmpty()) errors.rejectValue(nodo + ".forma_pago", "400", "La Forma De Pago No Es Válida");
        }
    }

    private void validarPeriodos(String periodo, Errors errors, String nodo) {
        Optional<CatPeriodos> catPeriodos = catPeriodos(periodo);
        if(catPeriodos.isEmpty()) errors.rejectValue(nodo + ".periodicidad","400","El Periodo De Pago No Es Válida");
    }

    private void validarDisponibilidadMensual(String descripcion, Errors errors, String nodo){
        Optional<PerfilTransaccionalMontos> perfilTransaccionalMontos = perfilTransaccionalMontos(descripcion);
        if(perfilTransaccionalMontos.isEmpty())
            errors.rejectValue(nodo + ".disponibilidad_mensual","400","La Disponibilidad Mensual No Es Válida");
    }

    private void validarPersonaRfcOrCurp(String rfc, String curp, String tipoPersona, Errors errors, String nodo){
        String regex_curp = "^[A-Z]{4}[0-9]{6}[HM]{1}[A-Z]{5}[A-Z0-9]{2}$";
        String regex_rfc = tipoPersona.equals("F") ? "^[A-ZÑ&]{4}[0-9]{6}[A-Z0-9]{3}$" : "^[A-ZÑ&]{3}[0-9]{6}[A-Z0-9]{3}$";
        if(!rfc.matches(regex_rfc) && rfc != null  && !rfc.isEmpty())
            errors.rejectValue(nodo + "rfc", "400", "El RFC No Tiene Un Formato Válido.");
        if(tipoPersona.equals("F") && !curp.matches(regex_curp))
            errors.rejectValue(nodo + "curp", "400", "La CURP No Tiene Un Formato Válido.");
        if(!tipoPersona.equals("F") && !tipoPersona.equals("M"))
            errors.rejectValue(nodo + "tipo_persona", "400", "Tipo De Persona No Válido: El Tipo De Persona Debe Ser 'F', 'M', 'FÍSICA' o 'MORAL'");
    }

    private void validarSexo(String sexo, Errors errors){
        if(sexo == null) {
            errors.rejectValue("sexo", "400", "El Campo 'Sexo' Es Requerido");
        } else if(sexo != null && !sexo.matches("^[FM]")) {
            errors.rejectValue("sexo", "400", "El Tipo De Persona Debe Ser 'F' (FEMENINO) o 'M' (MASCULINO)");
        }
    }

    private void validarFechaNacimiento(LocalDate fechaNacimiento, Errors errors){
        long edad = ChronoUnit.YEARS.between(fechaNacimiento, LocalDate.now());
        if(edad < 18) errors.rejectValue("fecha_nacimiento", "400", "El Cliente No Puede Ser Menor De 18 Años");
    }

    private void validarEntidadNacimiento(String entidad, Errors errors){
        if (entidad == null || entidad.isBlank())
            errors.rejectValue("entidad_nacimiento", "400", "La Entidad De Nacimiento No Puede Estar Vacía.");
        Optional<CatGeoEntidades> catGeoEntidades = entidades(entidad);
        if (catGeoEntidades.isEmpty())errors.rejectValue("entidad_nacimiento", "400", "Entidad De Nacimiento No Válida.");
    }

    private void validarPrefijoEstadoCivil(String prefijoBuro, Errors errors){
        Optional<CatEstadoCivil> catEstadoCivil = catEstadoCivil(prefijoBuro);
        if (catEstadoCivil.isEmpty()) errors.rejectValue("estado_civil","400","Estado Civil No Válido, Ingrese El Prefijo Correcto");
    }

    private Optional<PldPaises> paises(String pais){
        return pldPaisesRepository.findByNombre(pais);
    }

    private Optional<CatGeoEntidades> entidades(String entidad){
        return catGeoEntidadesRepository.findByEntidad(entidad);
    }

    private Optional<CatGeoMunicipios> municipios(String municipio, int entidad){
        return catGeoMunicipiosRepository.findByMunicipioAndFkCatEntidad(municipio, entidad);
    }

    private Optional<CatGeoLocalidades> localidades(String localidad, int municipio, int entidad){
        return catGeoLocalidadesRepository.findByLocalidadAndFkCatMunicipioAndFkCatEntidad(localidad, municipio, entidad);
    }

    private Optional<CatTipoVivienda> catTipoVivienda(String tipoVivienda){
        return catTipoViviendaRepository.findByDescripcion(tipoVivienda);
    }

    private Optional<CatTipoCasa> catTipoCasa(String tipoCasa){
        return catTipoCasaRepository.findByTipoCasa(tipoCasa);
    }

    private Optional<CatTipoViveZona> catTipoViveZona(String zonaResidencia){
        return catTipoViveZonaRepository.findByDescripcion(zonaResidencia);
    }

    private  Optional<CatTipoAsentamiento> catTipoAsentamiento(String tipoAsentamiento){
        return catTipoAsentamientoRepository.findByDescripcion(tipoAsentamiento);
    }

    private Optional<CatTipoVialidad> catTipoVialidad(String tipoVialidad){
        return catTipoVialidadRepository.findByTipoVialidad(tipoVialidad);
    }

    private Optional<CodigosPostales> codigosPostales(String codigoPostal, int fkCatEntidad, int fkCatMunicipio){
        return codigosPostalesRepository.findByCodigoPostalAndFkCatEntidadAndFkCatMunicipio(codigoPostal, fkCatEntidad, fkCatMunicipio);
    }

    private Optional<CatEstadoCivil> catEstadoCivil(String prefijoBuro) {
        return catEstadoCivilRepository.findByPrefijoBuro(prefijoBuro);
    }

    private Optional<CatDestinos> catDestinos(String destino) {
        return catDestinoRecursoRepository.findByDestino(destino);
    }

    private Optional<CatOrigenRecurso> catOrigenRecurso(String descripcion) {
        return catOrigenRecursoRepository.findByDescripcion(descripcion);
    }

    private Optional<CatPeriodos> catPeriodos(String periodo){
        return catPeriodosRepository.findByPeriodo(periodo);
    }

    private Optional<CatFormaPago> catFormaPago(String forma) {
        return catFormaPagoRepository.findByForma(forma);
    }

    private Optional<PerfilTransaccionalMontos> perfilTransaccionalMontos(String descripcion) {
        return perfilTransaccionalMontosRepository.findByDescripcion(descripcion);
    }
}
