<?php
!class_exists('CorreoElectronico') && require_once __DIR__ . '/correoElectronicoModel.php';

class Usuario
{
    public $apellidos;
    public $bloqueado;
    public $email;
    public $estado;
    public $fechaRegistro;
    public $foto;
    public $id;
    public $idIngreso;
    public $nombres;
    public $roles = [];
    public $sesionActiva = false;
    public $sesionActual;
    public $ultimoIngreso;
    public $username;
    public $expireSession = null;

    public function __construct()
    {
        !empty($this->fechaRegistro) && $this->fechaRegistro = date("d-m-Y h:i a", strtotime($this->fechaRegistro));
        $this->ultimoIngreso = !empty($this->ultimoIngreso) ? date("d-m-Y h:i a", strtotime($this->ultimoIngreso)) : 'Sin registros';
        $this->sesionActiva = !empty($this->sesionActual);
    }

    public function autenticar(string $contrasenia): object
    {
        $db = obtenerConexion();

        $respuesta = null;
        $intentosRestantes = null;
        $id = null;

        $query = "EXEC sistema.sp_autenticarUsuario :username,:contrasenia,:respuesta,:id,:intentosRestantes;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":username", $this->username, PDO::PARAM_STR);
        $stmt->bindParam(":contrasenia", $contrasenia, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);
        $stmt->bindParam(":id", $id, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 10);
        $stmt->bindParam(":intentosRestantes", $intentosRestantes, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 10);

        try {
            $stmt->execute();

            if ($respuesta == 'success') {
                $this->id = $id;
                return $this->iniciarSesion($db);
            }

            empty($id) && $id = '0';

            return (object)[
                'id' => base64_encode($id),
                'intentosRestantes' => $intentosRestantes,
                'message' => $respuesta,
                'status' => 200,
            ];
        } catch (PDOException $e) {
            return (object)[
                "message" => $e->getMessage(),
                "status" => 500,
            ];
        }
    }

    private function iniciarSesion($db, $fechaExpira = null, $jwt = null): object
    {
        $respuesta = null;
        $idIngreso = null;
        $ultimaSesion = null;

        $ipActual = obtenerIPActual();

        if (session_status() == PHP_SESSION_NONE)
            session_start();

        $sesionActual = session_id();

        $query = "EXEC sistema.sp_guardarIngreso :usuario,:sesion,:jwt,:ip,:respuesta,:idIngreso,:ultimaSesion;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":usuario", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":sesion", $sesionActual, PDO::PARAM_STR);
        $stmt->bindParam(":jwt", $jwt, PDO::PARAM_STR);
        $stmt->bindParam(":ip", $ipActual, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);
        $stmt->bindParam(":idIngreso", $idIngreso, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 10);
        $stmt->bindParam(":ultimaSesion", $ultimaSesion, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $usuario = (object)[];
            $stmt->execute();
            if ($respuesta == 'success') {
                if (!empty($ultimaSesion) && $ultimaSesion != $sesionActual) {
                    session_commit();
                    session_id($ultimaSesion);
                    session_start();
                    isset($_SESSION[INDICE_SESION]) && $_SESSION[INDICE_SESION]->sesionActiva = false;
                    session_commit();
                    session_id($sesionActual);
                    session_start();
                }

                $query = 'SELECT TOP(1) * FROM sistema.view_usuarios WHERE id = :id;';
                $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
                $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);

                $stmt->execute();
                $usuario = $stmt->fetchObject(__CLASS__);
                $usuario->idIngreso = $idIngreso;
                $usuario->expireSession = $fechaExpira;
                $_SESSION[INDICE_SESION] = $usuario;
            }

            return (object)[
                'id' => base64_encode($this->id),
                'message' => $respuesta,
                'status' => 200,
                'nombreCompleto' => $usuario->nombres . ' ' . $usuario->apellidos,
            ];
        } catch (PDOException $e) {
            return (object)[
                "message" => $e->getMessage(),
                "status" => 500,
            ];
        }
    }

    public function autenticarToken($db, string $token, $fechaExpira): object
    {
        return $this->iniciarSesion($db, $fechaExpira, $token);
    }

    public function cambiarContrasenia($contrasenia, $nuevaContrasenia, $origen = "Usuario"): object
    {
        $db = obtenerConexion();

        $respuesta = null;

        $query = "EXEC sistema.sp_cambiarContrasenia :id,:username,:contrasenia,:nuevaContrasenia,:origen,:respuesta;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":username", $this->username, PDO::PARAM_STR);
        $stmt->bindParam(":contrasenia", $contrasenia, PDO::PARAM_STR);
        $stmt->bindParam(":nuevaContrasenia", $nuevaContrasenia, PDO::PARAM_STR);
        $stmt->bindParam(":origen", $origen, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'message' => $respuesta,
                'status' => 200,
            ];
        } catch (PDOException $e) {
            return (object)[
                "message" => $e->getMessage(),
                "status" => 500,
            ];
        }
    }

    public function cerrarSesion(): object
    {
        $db = obtenerConexion();

        $respuesta = null;

        $query = 'EXEC sistema.sp_cerrarSesion :id,:idIngreso,:respuesta;';
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":idIngreso", $this->idIngreso, PDO::PARAM_INT);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            unset($_SESSION[INDICE_SESION]);

            return (object)[
                'message' => $respuesta,
                'status' => 200,
            ];
        } catch (PDOException $e) {
            return (object)[
                "message" => $e->getMessage(),
                "status" => 500,
            ];
        }
    }

    public static function obtenerTodos(): object
    {
        $db = obtenerConexion();

        $query = "SELECT * FROM sistema.view_usuarios WHERE estado = 1;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);

        try {
            $stmt->execute();

            $data = $stmt->fetchAll(PDO::FETCH_CLASS, __CLASS__);

            return (object)[
                "data" => $data,
                "status" => 200,
            ];
        } catch (PDOException $e) {
            return (object)[
                "message" => $e->getMessage(),
                "status" => 500,
            ];
        }
    }

    public function obtenerRoles(): object
    {
        $db = obtenerConexion();
        $query = "SELECT * FROM sistema.view_rolesUsuario WHERE usuario = :id;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);

        try {
            $stmt->execute();

            $data = $stmt->fetchAll(PDO::FETCH_OBJ);
            return (object)[
                'status' => 200,
                'data' => $data,
            ];
        } catch (PDOException $e) {
            return (object)[
                "message" => $e->getMessage(),
                "status" => 500,
            ];
        }
    }

    public static function verificarDisponibilidad($id, $username): object
    {
        $db = obtenerConexion();

        $respuesta = null;

        $query = "EXEC sistema.sp_verificarDisponibilidad :id,:username,:respuesta;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $id, PDO::PARAM_INT);
        $stmt->bindParam(":username", $username, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public function guardar($db = null, $contrasenia = null): object
    {
        if (empty($db)) $db = obtenerConexion();

        $respuesta = null;
        $id = null;

        $query = "EXEC sistema.sp_guardarUsuario :id,:username,:nombres,:apellidos,:email,:contrasenia,:respuesta,:idUsuario;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":username", $this->username, PDO::PARAM_STR);
        $stmt->bindParam(":nombres", $this->nombres, PDO::PARAM_STR);
        $stmt->bindParam(":apellidos", $this->apellidos, PDO::PARAM_STR);
        $stmt->bindParam(":email", $this->email, PDO::PARAM_STR);
        $stmt->bindParam(":contrasenia", $contrasenia, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);
        $stmt->bindParam(":idUsuario", $id, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 10);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta,
                'data' => (object)[
                    'id' => $id
                ]
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public function guardarRoles($db = null, $roles = []): object
    {
        if (empty($db)) $db = obtenerConexion();

        $respuesta = null;
        $strRoles = implode("@@@", $roles);

        $query = "EXEC sistema.sp_guardarRolesUsuario :id,:roles,:respuesta;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":roles", $strRoles, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public function crearContrasenia(): object
    {
        $solicitud = $this->crearSolicitudRestablecimiento($this->username, $this->email);

        if ($solicitud->status != 200 || ($solicitud->status == 200 && $solicitud->message != 'success')) return $solicitud;

        $envioCorreo = Usuario::enviarCorreoContrasenia($this->email, $solicitud->nombreCompleto, $solicitud->contrasenia);

        if ($envioCorreo->status != 200) return $envioCorreo;

        return (object)[
            'status' => 200,
            'message' => 'success',
            'email' => $this->email,
            'contrasenia' => $solicitud->contrasenia
        ];
    }

    private function crearSolicitudRestablecimiento($username, $email): object
    {
        $db = obtenerConexion();

        $respuesta = null;
        $id = null;
        $nombreCompleto = null;

        $str = obtenerStringRandom();
        $contrasenia = md5($str);

        $query = "EXEC sistema.sp_guardarSolicitudRestablecimiento :username,:email,:contrasenia,:respuesta,:idUsuario,:nombreCompleto;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":username", $username, PDO::PARAM_STR);
        $stmt->bindParam(":email", $email, PDO::PARAM_STR);
        $stmt->bindParam(":contrasenia", $contrasenia, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);
        $stmt->bindParam(":idUsuario", $id, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 10);
        $stmt->bindParam(":nombreCompleto", $nombreCompleto, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta,
                'contrasenia' => $str,
                'idSolicitud' => $id,
                'nombreCompleto' => $nombreCompleto
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public static function enviarCorreoContrasenia($email, $nombreCompleto, $contrasenia = "ABC"): object
    {
        $urlPlantilla = __DIR__ . '/../../docs/plantillas/correoContrasenia.html';

        if (!file_exists($urlPlantilla)) return (object)[
            'status' => 500,
            'message' => 'No se encontró la plantilla de correo'
        ];

        $correo = new CorreoElectronico($urlPlantilla, ['nombreCompleto' => $nombreCompleto, 'contrasenia' => $contrasenia]);
        $asunto = mb_strtoupper("Restablecimiento de contraseña", "UTF-8");

        return $correo->enviar($email, $nombreCompleto, $asunto, [], 'soporte@constru-services.com');
    }

    public function cambiarEstado($nuevoEstdo): object
    {
        $db = obtenerConexion();

        $respuesta = null;

        $query = "EXEC sistema.sp_cambiarEstadoUsuario :id,:estado,:respuesta;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":estado", $nuevoEstdo, PDO::PARAM_INT);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public function eliminar($usuarioResponsable): object
    {
        $db = obtenerConexion();

        $respuesta = null;

        $query = "EXEC sistema.sp_eliminarUsuario :id,:usuarioResponsable,:respuesta;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":usuarioResponsable", $usuarioResponsable, PDO::PARAM_INT);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public function actualizarFoto($nombreArchivo): object
    {
        $db = obtenerConexion();

        $respuesta = null;

        $query = "EXEC sistema.sp_actualizarFotoUsuario :id,:nombreArchivo,:respuesta;";
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":nombreArchivo", $nombreArchivo, PDO::PARAM_STR);
        $stmt->bindParam(":respuesta", $respuesta, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 300);

        try {
            $stmt->execute();

            return (object)[
                'status' => 200,
                'message' => $respuesta,
                'nombreArchivo' => $nombreArchivo
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }

    public static function obtenerTodosParaCombo(): object
    {
        $solicitud = self::obtenerTodos();

        if ($solicitud->status != 200) {
            return $solicitud;
        }

        $usuarios = array_map(function ($usuario) {
            return (object)[
                'id' => $usuario->id,
                'nombreCompleto' => $usuario->nombres . ' ' . $usuario->apellidos
            ];
        }, $solicitud->data);

        return (object)[
            'status' => 200,
            'message' => 'success',
            'data' => array_values($usuarios)
        ];
    }

    public static function obtenerPorModulo(int $modulo): object
    {
        $db = obtenerConexion();

        $query = 'EXEC sistema.sp_obtenerUsuariosPorModulo :modulo';
        $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
        $stmt->bindParam(':modulo', $modulo, PDO::PARAM_INT);

        try {
            $stmt->execute();
            $result = $stmt->fetchAll(PDO::FETCH_OBJ);

            foreach ($result as $key => $row) {
                $result[$key]->fechaRegistro = date('d-m-Y h:i a', strtotime($row->fechaRegistro));
            }

            return (object)['status' => 200, 'message' => 'success', 'data' => $result];
        } catch (PDOException $e) {
            return (object)['status' => 500, 'message' => $e->getMessage()];
        }
    }

    public static function obtenerAsignacionesPorModulo($usuario, $modulo, $contenedor = 'none'): object
    {
        $asignaciones = [];
        $empresasAsignadas = [];

        $db = obtenerConexion();

        switch ($contenedor) {
            case 'RRHH':
                !class_exists('TipoPlanilla') && require_once __DIR__ . '/../../RRHH/code/models/tipoPlanillaModel.php';
                $requestTiposPlanilla = TipoPlanilla::obtenerPorUsuario($usuario, $db);
                if ($requestTiposPlanilla->status != 200) {
                    return $requestTiposPlanilla;
                }

                $asignaciones = $requestTiposPlanilla->data;
                break;
        }

        !class_exists('Empresa') && require_once __DIR__ . '/empresaModel.php';
        !class_exists('Sucursal') && require_once __DIR__ . '/sucursalModel.php';
        !class_exists('Proyecto') && require_once __DIR__ . '/proyectoModel.php';

        $requestEmpresas = Empresa::obtenerPorUsuarioModulo($usuario, $modulo, $db);

        if ($requestEmpresas->status != 200) {
            return $requestEmpresas;
        }

        $empresasAsignadas = $requestEmpresas->data;


        foreach ($empresasAsignadas as $keyEmpresa => $empresa) {
            $requestSucursales = Sucursal::obtenerPorUsuarioModulo($usuario, $empresa->id, $db);

            if ($requestSucursales->status != 200) {
                return $requestSucursales;
            }

            $sucursalesAsignadas = $requestSucursales->data;

            foreach ($sucursalesAsignadas as $keySucursal => $sucursal) {
                $requestProyectos = Proyecto::obtenerPorUsuarioModulo($usuario, $sucursal->id, $db);

                if ($requestProyectos->status != 200) {
                    return $requestProyectos;
                }

                $proyectosAsignados = $requestProyectos->data;

                $sucursalesAsignadas[$keySucursal]->proyectos = $proyectosAsignados;
            }

            $empresasAsignadas[$keyEmpresa]->sucursales = $sucursalesAsignadas;
        }

        if (!empty($asignaciones)) {
            $keySuperior = '';

            switch ($contenedor) {
                case 'RRHH':
                    $keySuperior = 'codtipoplanilla';
                    break;
            }

            foreach ($asignaciones as $keyAsignacion => $asignacion) {
                $empresas = array_values(array_filter($empresasAsignadas, function ($empresa) use ($keySuperior, $asignacion) {
                    return intval($empresa->idSuperior) == intval($asignacion->{$keySuperior});
                }));

                $asignaciones[$keyAsignacion]->empresas = $empresas;
            }
        }

        if (empty($asignaciones)) {
            $asignaciones = $empresasAsignadas;
        }

        return (object)['status' => 200, 'message' => 'success', 'data' => $asignaciones];
    }

    public function solicitarRestablecimientoContrasenia(): object
    {
        $procesoSolicitud = $this->crearSolicitudRestablecimiento($this->username, $this->email);

        if ($procesoSolicitud->status == 200 && $procesoSolicitud->message == 'success') {
            $envio = Usuario::enviarCorreoContrasenia($this->email, $procesoSolicitud->nombreCompleto, $procesoSolicitud->contrasenia);
            if ($envio->status == 200) {
                $respuesta = new stdClass();
                $respuesta->status = 200;
                $respuesta->message = 'correoEnviado';
                $respuesta->email = $this->email;
                return $respuesta;
            } else {
                return $envio;
            }
        } else {
            return $procesoSolicitud;
        }
    }

    public static function obtenerPorUsername($db = null, $username): object
    {
        try {
            if (empty($db)) $db = obtenerConexion();

            $query = "SELECT TOP(1) * FROM sistema.view_usuarios WHERE username = :username AND estado = 1 and bloqueado = 0;";
            $stmt = $db->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
            $stmt->bindParam(":username", $username, PDO::PARAM_STR);

            $stmt->execute();

            $usuario = $stmt->fetchObject(__CLASS__);

            return (object)[
                'status' => 200,
                'message' => 'success',
                'data' => $usuario
            ];
        } catch (PDOException $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        } catch (Exception $e) {
            return (object)[
                'status' => 500,
                'message' => $e->getMessage()
            ];
        }
    }
}
