import base64
import io
import json
import hashlib
import tempfile
import pandas as pd
import traceback
import xlrd

from openpyxl import Workbook
import openpyxl
from openpyxl.worksheet.datavalidation import DataValidation

from datetime import datetime
from decouple import config as load_data
from flask import request, jsonify, Blueprint
from lib.Stech import HTTP404Error, Logger, Stech

from sqlalchemy import Integer, String, update, func, and_, or_, case, Date, cast, literal_column, text
from sqlalchemy.orm import aliased
from src.crons.LoadInfoVehicle import LoadInfoVehicle
from src.decorators import verify_token, write_token

from src.models.recuperautos.Comunas import Comunas
from src.models.recuperautos.GruposUsuarios import GruposUsuario
from src.models.recuperautos.ReportesPatente import ReportesPatente
from src.models.recuperautos.UsuarioInstituciones import UsuarioInstituciones
from src.models.recuperautos.ComunasAledanas import ComunasAledanas
from src.utils.Utils import Utils

main_create_user = Blueprint('main_create_user', __name__)
main_login = Blueprint('main_login', __name__)
main_create_comunas_aledanas = Blueprint('main_create_comunas_aledanas', __name__)
main_get_comunas_aledanas = Blueprint('main_get_comunas_aledanas', __name__)
main_reporte_puntos = Blueprint('main_reporte_puntos', __name__)
main_load_info_vehicle = Blueprint('main_load_info_vehicle', __name__)
main_bulk_pload_reports = Blueprint('main_bulk_pload_reports', __name__)


@main_bulk_pload_reports.route('/', methods=['POST'])
@verify_token
def bulk_pload_reports():

    try:

        session = Stech.get_session(load_data('ENVIRONMENTS'))

        data = request.get_json()
        file_base64 = data.get('file_base64')

        decode_data = base64.b64decode(file_base64)
        excel_file = io.BytesIO(decode_data)
        df_excel = pd.read_excel(excel_file, sheet_name=None)

        hojas_excel = df_excel.keys()

        datos_por_hoja = {}
        for hoja in hojas_excel:
            df_hoja = pd.read_excel(excel_file, sheet_name=hoja, skiprows=0)

            # Reemplazar NaN y NaT por '--' en esta hoja
            df_hoja.fillna('--', inplace=True)
            df_hoja.replace(pd.NaT, '--', inplace=True)

            # Guarda los datos de la hoja actual en un array
            datos_hoja = []
            for index, row in df_hoja.iterrows():
                datos_hoja.append(row.to_dict())

            # Agrega los datos de la hoja actual al diccionario
            datos_por_hoja[hoja] = datos_hoja
        
        list_not_found = []

        # Validar cada JSON generado
        count_save = 0
        count_no_save = 0
        for datos_json in datos_por_hoja["Vehiculos Reportados"]:
            se_puede_guardar = True
            campos_con_dash = []

            address = f"{ datos_json['DIRECCION'] }, { datos_json['COMUNA'] }, { datos_json['REGION'] }, Chile"
            lat, lng = Stech.getLatitudeLongitudeLocal(address)

            lat = lat or 0
            lng = lng or 0

            if lat == 0 and lng == 0:
                se_puede_guardar = False
                campos_con_dash.append("Problemas en la dirección,")

            object_comuna = session.query(Comunas).filter_by(nombre_comuna=datos_json['COMUNA']).first()
            if not object_comuna:
                se_puede_guardar = False
                campos_con_dash.append("No se encontró la comuna en la base de datos.")

            object_user = session.query(UsuarioInstituciones).filter_by(email=datos_json['EMAIL']).first()
            if not object_user:
                se_puede_guardar = False
                campos_con_dash.append("No se encontró el usuario en la base de datos.")

            if len(datos_json['PATENTE']) > 3 and len(datos_json['PATENTE']) < 7:
                pass
            else:
                se_puede_guardar = False
                campos_con_dash.append("Patente no valida.")

            for campo, valor in datos_json.items():
                if valor == '--':
                    se_puede_guardar = False
                    campos_con_dash.append(campo)

            if se_puede_guardar:

                comuna_id = object_comuna.id_comuna
                usuario_id = object_user.id_usuario

                # Conviértelo a un objeto datetime de Python
                fecha = (datos_json['FECHA']).to_pydatetime()

                query = text("SELECT public.insertreportados_v2(:fecha, :patente, :motirobo, :latitud, :longitud, :url_imagen, :responsable, :id_comuna, :dire, :app_user)")
        
                session.execute(
                    query,
                    {
                        'fecha': fecha.strftime('%Y-%m-%d'),
                        'patente': str(datos_json['PATENTE']),
                        'motirobo': str(datos_json['MOTIVO_ROBO']),
                        'latitud': float(lat),
                        'longitud': float(lng),
                        'url_imagen': str('https://media.recuperauto.cl/Media/Recuperauto/noimagecar1.png'),
                        'responsable': str(datos_json['EMAIL']),
                        'id_comuna': int(comuna_id),
                        'dire': str(datos_json['DIRECCION']),
                        'app_user': int(usuario_id)
                    }
                ).fetchall()

                session.commit()
                count_save += 1
            else:

                list_not_found.append({
                    "patente": datos_json,
                    "error": f"faltan los siguientes datos: {', '.join(campos_con_dash)}"
                })
                count_no_save += 1

        total = len(datos_por_hoja["Vehiculos Reportados"])
        
        return jsonify({
            "success": "true", 
            "message": f"Total: {total}, Guardados: {count_save}, No guardados: {count_no_save}" , 
            "list_error": list_not_found
        }), 200
    
    except Exception as ex:
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': "false"})
        return response, 500


@main_load_info_vehicle.route('/', methods=['GET'])
@verify_token
def load_info_vehicle():
    try:
        LoadInfoVehicle.load_data_vehicle()
        return jsonify({"success": "true"}), 200
    
    except Exception as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': False})
        return response, 500


@main_reporte_puntos.route('/', methods=['POST'])
def reporte_puntos():
    session = Stech.get_session(load_data('ENVIRONMENTS'))

    try:
        data = request.get_json()

        result_list = []

        rpat = aliased(ReportesPatente)
        gru = aliased(GruposUsuario)

        with session:
            query = (
                session.query(
                    rpat.rpat_lat,
                    rpat.rpat_lng,
                    rpat.fecha_reporte
                )
                .select_from(rpat)
                .join(gru, gru.id_grupo == rpat.group_id)
                .filter(gru.tipo_grupo == "reportes")
                .filter(gru.nombre == data['patente'])
            )

            result = query.all()
        
        for row in result:
            result_list.append({
                "latitud": row[0],
                "longitud": row[1],
                "fecha_reporte": row[2],
            })

        data_response = {
            'puntos': result_list,
            'success': True
        }

        return jsonify(data_response), 200

    except Exception as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': False})
        
        return response, 500


@main_get_comunas_aledanas.route('/', methods=['POST'])
def get_comunas_aledanas():
    session = Stech.get_session(load_data('ENVIRONMENTS'))

    try:
        data = request.get_json()

        comunas_aledanas = session.query(ComunasAledanas).filter_by(comuna_id=data['comuna_id']).first()
        
        if not comunas_aledanas:
            raise ValueError("No existen comunas aledañas para la comuna consultada")

        if not comunas_aledanas:
            raise ValueError("No existen comunas aledañas para la comuna consultada")
        
        json_comunas_aledanas = json.loads(comunas_aledanas.json_comunas_aledana)
        data_response = {
            'message': json_comunas_aledanas, 
            'lat_lng': comunas_aledanas.lat_lng, 
            'success': True
        }

        return jsonify(data_response), 200
    
    except ValueError as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Not Found", "error": str(ex), 'success': False})
        
        return response, 404

    except Exception as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': False})
        
        return response, 500


@main_create_comunas_aledanas.route('/', methods=['POST'])
@verify_token
def create_comunas_aledanas():
    session = Stech.get_session(load_data('ENVIRONMENTS'))

    try:

        data = request.get_json()

        existing_user = session.query(ComunasAledanas).filter_by(comuna_id=data['comuna_id']).scalar()
        if existing_user:
            return jsonify({'message': 'Esta comuna ya tiene comunas aledañas agregadas. No se pudieron crear.', 'success': False}), 400
        
        new_comunas_aledanas = ComunasAledanas(
            comuna_id = data['comuna_id'],
            json_comunas_aledana = data['json_comunas_aledana']
        )

        # Agrega el nuevo usuario a la sesión y realiza la confirmación para guardar en la base de datos
        session.add(new_comunas_aledanas)
        session.commit()

        data_response = {
            'message': 'Usuario creado con éxito', 
            'success': True
        }

        return jsonify(data_response), 201

    except Exception as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': False})
        
        return response, 500


@main_create_user.route('/', methods=['POST'])
@verify_token
def create_user():

    session = Stech.get_session(load_data('ENVIRONMENTS'))

    try:        
        data = request.get_json()

        existing_user = session.query(UsuarioInstituciones).filter_by(email=data['email']).scalar()
        if existing_user:
            data_error = {
                'message': 'El correo electrónico ya está en uso. No se pudo crear el usuario.',
                'error': 'EXISTS_MAIL',
            }
            return jsonify(data_error), 400
        
        # Crea un nuevo usuario
        new_user = UsuarioInstituciones(
            email=data['email'],
            first_name=data['first_name'],
            last_name=data['last_name'],
            region_usuario=data['region_usuario'],
            comuna_usuario=data['comuna_usuario'],
            cargo_usuario=data['cargo_usuario'],
            rut_usuario=data['rut_usuario'],
            password= Stech.make_password(data['password']),
            fecha_registro=datetime.now(),
            permiso=data['permiso'],
            telefono=data['telefono'],
        )

        # Agrega el nuevo usuario a la sesión y realiza la confirmación para guardar en la base de datos
        session.add(new_user)
        session.commit()

        data_response = {
            'message': 'Usuario creado con éxito', 
            'success': True
        }

        return jsonify(data_response), 201

    except Exception as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': False})
        
        return response, 500


@main_login.route('/', methods=['POST'])
def login():

    try:
        session = Stech.get_session(load_data('ENVIRONMENTS'))
        data = request.get_json()

        email = data.get('email')
        password = data.get('password')

        if not email or not password:
            return jsonify({'message': 'Se requiere un correo electrónico y una contraseña válidos.'}), 400

        authenticated, user = authenticate_user(email, password)

        if authenticated:
            # Aquí puedes devolver la información del usuario autenticado como respuesta

            com_aledana = session.query(ComunasAledanas).filter_by(comuna_id=user.comuna_usuario).first()

            notSensibleData = {
                'mail': user.email,
                'first_name': user.first_name,
                'last_name': user.last_name,
            }
            token = write_token(data=notSensibleData)

            response_data = {
                "message": "Inicio de sesión exitoso",
                "id_usuario": user.id_usuario,
                "first_name": user.first_name.title(),
                "last_name": user.last_name.title(),
                "rut_usuario": user.rut_usuario.upper(),
                "comuna_usuario": user.comuna_usuario,
                "region_usuario": user.region_usuario,
                "permiso": user.permiso,
                "token": str(token.decode('utf-8')),
                "lat_lng": com_aledana.lat_lng if com_aledana.lat_lng else "--",
                "telefono": user.telefono if user.telefono else "--",
                "cargo_usuario": user.cargo_usuario
            }

            return jsonify(response_data), 200
        
        else:
            return jsonify({'message': 'Credenciales inválidas. Inicio de sesión fallido.'}), 401
                
    except HTTP404Error as ex:
        response = jsonify(
            {'message': f"{ str(ex) }", 'success': False})
        return response, 404

    except Exception as ex:
        Logger.add_to_log("error", str(ex))
        Logger.add_to_log("error", traceback.format_exc())
        response = jsonify(
            {'message': "Internal Server Error", "error": f"{str(ex)} - {str(traceback.format_exc())}", 'success': False})
        return response, 500


def authenticate_user(email, password):

    session = Stech.get_session(load_data('ENVIRONMENTS'))

    # Buscar el usuario por su correo electrónico en la tabla UsuariosInstituciones
    user = session.query(UsuarioInstituciones).filter_by(email=email).first()

    if user:
        # Obtener la contraseña encriptada y la sal almacenada en la tabla
        hashed_password, salt = user.password.split('$')

        # Combinar la contraseña ingresada con la sal almacenada
        password_with_salt = password + salt

        # Aplicar el algoritmo de hash (en este caso, SHA-256)
        hashed_password_entered = hashlib.sha256(password_with_salt.encode()).hexdigest()

        # Verificar si la contraseña ingresada coincide con la contraseña almacenada
        if hashed_password == hashed_password_entered:
            return True, user  # Usuario autenticado correctamente, devuelve el usuario

    return False, None  # Usuario no autenticado