<?php
/**
 * Autenticación y gestión de usuarios (reemplaza usuarios de WordPress).
 */

final class JB_Auth {
	private static $current_user = null;
	private static $users_table = '';
	private static $meta_table = '';
	private static $roles_table = '';
	private static $capabilities = array();

	public static function init() {
		global $wpdb;
		self::$users_table = $wpdb->prefix . 'users';
		self::$meta_table  = $wpdb->prefix . 'usermeta';
		self::$roles_table = $wpdb->prefix . 'user_roles';

		if ( session_status() === PHP_SESSION_NONE && PHP_SAPI !== 'cli' ) {
			$lifetime = self::session_lifetime();

			/*
			 * Importante en XAMPP/hosting compartido:
			 * El GC de PHP borra archivos de sesión según session.gc_maxlifetime del
			 * proceso que limpia (por defecto 1440 s = 24 min). Si usamos C:\xampp\tmp
			 * u otra carpeta compartida, otras apps pueden borrar nuestra sesión.
			 * Guardamos en storage/sessions propio de la app.
			 */
			$save_path = self::ensure_session_save_path();
			if ( $save_path ) {
				session_save_path( $save_path );
			}

			@ini_set( 'session.gc_maxlifetime', (string) $lifetime );
			@ini_set( 'session.cookie_lifetime', (string) $lifetime );
			@ini_set( 'session.use_strict_mode', '1' );
			@ini_set( 'session.use_only_cookies', '1' );

			$cfg = JB_App::config( 'session' );
			session_name( $cfg['name'] ?? 'jb_planeacion_session' );
			session_set_cookie_params(
				array(
					'lifetime' => $lifetime,
					'path'     => '/',
					'httponly' => true,
					'samesite' => 'Lax',
					'secure'   => self::request_is_https(),
				)
			);
			session_start();
		}

		if ( ! empty( $_SESSION['jb_user_id'] ) ) {
			self::$current_user = self::get_userdata( (int) $_SESSION['jb_user_id'] );
			if ( self::$current_user ) {
				$_SESSION['jb_last_activity'] = time();
				self::touch_session_cookie();
			} else {
				unset( $_SESSION['jb_user_id'], $_SESSION['jb_last_activity'] );
			}
		}
	}

	/**
	 * Tiempo de vida de sesión en segundos (mínimo 1 h).
	 *
	 * @return int
	 */
	public static function session_lifetime() {
		$cfg      = JB_App::config( 'session' );
		$lifetime = (int) ( $cfg['lifetime'] ?? ( 86400 * 14 ) );
		return max( 3600, $lifetime );
	}

	/**
	 * Carpeta exclusiva de sesiones de JB Planeación.
	 *
	 * @return string|null Ruta absoluta o null si no se pudo preparar.
	 */
	private static function ensure_session_save_path() {
		$root = defined( 'JB_APP_ROOT' ) ? JB_APP_ROOT : dirname( __DIR__ ) . '/';
		$dir  = rtrim( str_replace( '\\', '/', $root ), '/' ) . '/storage/sessions';

		if ( ! is_dir( $dir ) ) {
			if ( ! @mkdir( $dir, 0755, true ) && ! is_dir( $dir ) ) {
				return null;
			}
		}

		$htaccess = $dir . '/.htaccess';
		if ( ! is_file( $htaccess ) ) {
			@file_put_contents( $htaccess, "Require all denied\nDeny from all\n" );
		}

		$index = $dir . '/index.html';
		if ( ! is_file( $index ) ) {
			@file_put_contents( $index, '' );
		}

		return is_writable( $dir ) ? $dir : null;
	}

	/**
	 * @return bool
	 */
	private static function request_is_https() {
		if ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== strtolower( (string) $_SERVER['HTTPS'] ) ) {
			return true;
		}
		if ( isset( $_SERVER['SERVER_PORT'] ) && 443 === (int) $_SERVER['SERVER_PORT'] ) {
			return true;
		}
		if ( ! empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === strtolower( (string) $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
			return true;
		}
		return false;
	}

	public static function create_tables() {
		global $wpdb;
		$cc = $wpdb->get_charset_collate();

		dbDelta( "CREATE TABLE {$wpdb->prefix}users (
			ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			user_login varchar(60) NOT NULL DEFAULT '',
			user_pass varchar(255) NOT NULL DEFAULT '',
			user_nicename varchar(50) NOT NULL DEFAULT '',
			user_email varchar(100) NOT NULL DEFAULT '',
			user_registered datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			display_name varchar(250) NOT NULL DEFAULT '',
			PRIMARY KEY (ID),
			UNIQUE KEY user_login (user_login),
			KEY user_email (user_email)
		) $cc;" );

		dbDelta( "CREATE TABLE {$wpdb->prefix}usermeta (
			umeta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			user_id bigint(20) unsigned NOT NULL DEFAULT 0,
			meta_key varchar(255) DEFAULT NULL,
			meta_value longtext,
			PRIMARY KEY (umeta_id),
			KEY user_id (user_id),
			KEY meta_key (meta_key(191))
		) $cc;" );

		dbDelta( "CREATE TABLE {$wpdb->prefix}user_roles (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			role_slug varchar(100) NOT NULL,
			capability varchar(100) NOT NULL,
			PRIMARY KEY (id),
			UNIQUE KEY role_cap (role_slug, capability)
		) $cc;" );

		dbDelta( "CREATE TABLE {$wpdb->prefix}options (
			option_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			option_name varchar(191) NOT NULL DEFAULT '',
			option_value longtext NOT NULL,
			autoload varchar(20) NOT NULL DEFAULT 'yes',
			PRIMARY KEY (option_id),
			UNIQUE KEY option_name (option_name)
		) $cc;" );
	}

	public static function get_userdata( $user_id ) {
		global $wpdb;
		$user = $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM `" . self::$users_table . "` WHERE ID = %d", $user_id )
		);
		if ( ! $user ) {
			return false;
		}
		$user->roles = self::get_user_roles( $user_id );
		$user->caps  = self::get_user_capabilities( $user_id );
		return new WP_User( $user );
	}

	public static function get_user_by( $field, $value ) {
		global $wpdb;
		$allowed = array( 'id', 'login', 'email', 'slug' );
		$field   = strtolower( $field );
		if ( 'id' === $field ) {
			return self::get_userdata( (int) $value );
		}
		$col = 'login' === $field ? 'user_login' : ( 'email' === $field ? 'user_email' : 'user_nicename' );
		$user = $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM `" . self::$users_table . "` WHERE `{$col}` = %s LIMIT 1", $value )
		);
		if ( ! $user ) {
			return false;
		}
		$user->roles = self::get_user_roles( $user->ID );
		$user->caps  = self::get_user_capabilities( $user->ID );
		return $user;
	}

	/**
	 * @param array<string, mixed> $args Query args (role__in, orderby, order).
	 * @return WP_User[]
	 */
	public static function get_users( $args = array() ) {
		global $wpdb;

		$args = wp_parse_args(
			is_array( $args ) ? $args : array(),
			array(
				'orderby' => 'display_name',
				'order'   => 'ASC',
			)
		);

		$joins  = '';
		$where  = array( '1=1' );
		$params = array();

		$include_deleted = ! empty( $args['include_deleted'] );
		if ( ! $include_deleted ) {
			$where[] = "NOT EXISTS (
				SELECT 1 FROM `" . self::$meta_table . "` AS um_deleted
				WHERE um_deleted.user_id = u.ID
				AND um_deleted.meta_key = 'jb_user_deleted'
				AND um_deleted.meta_value = '1'
			)";
		}

		if ( ! empty( $args['role__in'] ) && is_array( $args['role__in'] ) ) {
			$roles = array_values(
				array_filter(
					array_map( 'sanitize_key', $args['role__in'] )
				)
			);

			if ( ! empty( $roles ) ) {
				$placeholders = implode( ',', array_fill( 0, count( $roles ), '%s' ) );
				$joins         = " INNER JOIN `" . self::$meta_table . "` AS um_role ON um_role.user_id = u.ID AND um_role.meta_key = 'logistica_jb_role' ";
				$where[]       = "um_role.meta_value IN ({$placeholders})";
				$params        = array_merge( $params, $roles );
			}
		}

		$allowed_orderby = array( 'display_name', 'user_login', 'user_email', 'user_registered', 'ID' );
		$orderby_col     = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'display_name';
		$order           = 'DESC' === strtoupper( (string) $args['order'] ) ? 'DESC' : 'ASC';

		$sql = 'SELECT DISTINCT u.* FROM `' . self::$users_table . '` AS u'
			. $joins
			. ' WHERE ' . implode( ' AND ', $where )
			. ' ORDER BY u.`' . $orderby_col . '` ' . $order;

		if ( ! empty( $params ) ) {
			$sql = $wpdb->prepare( $sql, $params );
		}

		$rows  = $wpdb->get_results( $sql );
		$users = array();

		foreach ( $rows as $row ) {
			$user = self::get_userdata( (int) $row->ID );
			if ( $user ) {
				$users[] = $user;
			}
		}

		return $users;
	}

	/**
	 * @return list<WP_User>
	 */
	public static function list_all_users() {
		return self::get_users(
			array(
				'orderby' => 'display_name',
				'order'   => 'ASC',
			)
		);
	}

	public static function get_user_roles( $user_id ) {
		$jb_role = get_user_meta( $user_id, 'logistica_jb_role', true );
		if ( $jb_role ) {
			return array( $jb_role );
		}
		if ( self::user_has_cap( $user_id, 'manage_options' ) ) {
			return array( 'administrator' );
		}
		return array();
	}

	public static function get_user_capabilities( $user_id ) {
		$caps = array();
		if ( self::user_has_cap( $user_id, 'manage_options' ) ) {
			$caps['manage_options'] = true;
		}
		$jb_role = get_user_meta( $user_id, 'logistica_jb_role', true );
		if ( $jb_role ) {
			$caps[ $jb_role ] = true;
			$caps['logistica_access'] = true;
			foreach ( self::role_capabilities( $jb_role ) as $cap ) {
				$caps[ $cap ] = true;
			}
		}
		$overrides = get_user_meta( $user_id, 'logistica_user_tab_overrides', true );
		if ( is_array( $overrides ) ) {
			$caps['logistica_tab_overrides'] = $overrides;
		}
		return $caps;
	}

	public static function role_capabilities( $role_slug ) {
		global $wpdb;
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT capability FROM `" . self::$roles_table . "` WHERE role_slug = %s",
				$role_slug
			)
		);
		$caps = array();
		foreach ( $rows as $row ) {
			$caps[] = $row->capability;
		}
		return $caps;
	}

	public static function add_role_capability( $role_slug, $capability ) {
		global $wpdb;
		$exists = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT id FROM `" . self::$roles_table . "` WHERE role_slug = %s AND capability = %s",
				$role_slug,
				$capability
			)
		);
		if ( ! $exists ) {
			$wpdb->insert( self::$roles_table, array(
				'role_slug'   => $role_slug,
				'capability'  => $capability,
			) );
		}
	}

	public static function user_has_cap( $user_id, $cap ) {
		if ( 'manage_options' === $cap ) {
			global $wpdb;
			$is_admin = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT umeta_id FROM `" . self::$meta_table . "` WHERE user_id = %d AND meta_key = 'jb_is_admin' AND meta_value = '1'",
					$user_id
				)
			);
			return (bool) $is_admin;
		}
		$jb_role = get_user_meta( $user_id, 'logistica_jb_role', true );
		if ( 'logistica_manage_config' === $cap && ( $jb_role === 'jb_administrador' || self::user_has_cap( $user_id, 'manage_options' ) ) ) {
			return true;
		}
		if ( 0 === strpos( $cap, 'logistica_tab_' ) ) {
			// Delegado a Logistica_Roles en runtime.
			return false;
		}
		if ( 'logistica_access' === $cap ) {
			return (bool) $jb_role || self::user_has_cap( $user_id, 'manage_options' );
		}
		if ( 'upload_files' === $cap ) {
			return self::user_has_cap( $user_id, 'manage_options' ) || (bool) $jb_role;
		}
		global $wpdb;
		if ( $jb_role ) {
			$has = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT id FROM `" . self::$roles_table . "` WHERE role_slug = %s AND capability = %s",
					$jb_role,
					$cap
				)
			);
			if ( $has ) {
				return true;
			}
		}
		return false;
	}

	public static function signon( $credentials, $secure = false ) {
		$login    = $credentials['user_login'] ?? '';
		$password = $credentials['user_password'] ?? '';
		$remember = ! empty( $credentials['remember'] );

		if ( '' === $login || '' === $password ) {
			return new WP_Error( 'empty_username', 'Usuario o contraseña vacíos.' );
		}

		$user = self::get_user_by( 'login', $login );
		if ( ! $user ) {
			$user = self::get_user_by( 'email', $login );
		}
		if ( ! $user ) {
			return new WP_Error( 'invalid_username', 'Usuario no encontrado.' );
		}
		if ( ! JB_Password::verify( $password, $user->user_pass ) ) {
			return new WP_Error( 'incorrect_password', 'Contraseña incorrecta.' );
		}

		if ( class_exists( 'Logistica_User_Lifecycle', false ) && Logistica_User_Lifecycle::is_deleted( $user->ID ) ) {
			return new WP_Error( 'deleted_user', __( 'Este usuario fue desactivado. Contacta al administrador.', 'logistica' ) );
		}

		// Rehash si venía de WordPress ($wp$) hacia bcrypt nativo.
		if ( str_starts_with( (string) $user->user_pass, '$wp$' ) || str_starts_with( (string) $user->user_pass, '$P$' ) ) {
			self::update_password( $user->ID, $password );
		}

		self::set_current_user( $user->ID, $remember );
		return $user;
	}

	public static function set_current_user( $user_id, $remember = false ) {
		self::$current_user = self::get_userdata( $user_id );
		if ( ! self::$current_user ) {
			self::$current_user = null;
			return;
		}
		$_SESSION['jb_user_id'] = $user_id;
		self::touch_session_cookie();
	}

	/**
	 * Renueva la caducidad de la cookie de sesión en el navegador.
	 *
	 * @return void
	 */
	public static function touch_session_cookie() {
		if ( session_status() !== PHP_SESSION_ACTIVE || PHP_SAPI === 'cli' || headers_sent() ) {
			return;
		}

		$lifetime = self::session_lifetime();
		$params   = session_get_cookie_params();
		setcookie(
			session_name(),
			session_id(),
			array(
				'expires'  => time() + $lifetime,
				'path'     => $params['path'] ?? '/',
				'domain'   => $params['domain'] ?? '',
				'secure'   => ! empty( $params['secure'] ) || self::request_is_https(),
				'httponly' => true,
				'samesite' => $params['samesite'] ?? 'Lax',
			)
		);

		if ( ! empty( $_SESSION['jb_user_id'] ) ) {
			$_SESSION['jb_last_activity'] = time();
		}
	}

	public static function logout() {
		self::$current_user = null;
		unset( $_SESSION['jb_user_id'] );
		if ( session_status() === PHP_SESSION_ACTIVE ) {
			session_destroy();
		}
	}

	public static function current_user() {
		return self::$current_user;
	}

	public static function create_user( $login, $password, $email, $display_name = '', $is_admin = false ) {
		global $wpdb;
		$hash = JB_Password::hash( $password );
		$wpdb->insert( self::$users_table, array(
			'user_login'    => $login,
			'user_pass'     => $hash,
			'user_nicename' => sanitize_title( $login ),
			'user_email'    => $email,
			'display_name'  => $display_name ?: $login,
		) );
		$id = (int) $wpdb->insert_id;
		if ( $is_admin ) {
			update_user_meta( $id, 'jb_is_admin', '1' );
		}
		return $id;
	}

	/**
	 * @param int    $user_id  ID de usuario.
	 * @param string $password Nueva contraseña en texto plano.
	 * @return bool
	 */
	public static function update_password( $user_id, $password ) {
		global $wpdb;
		return (bool) $wpdb->update(
			self::$users_table,
			array( 'user_pass' => JB_Password::hash( $password ) ),
			array( 'ID' => (int) $user_id )
		);
	}
}

function is_user_logged_in() {
	return JB_Auth::current_user() !== null;
}

function get_current_user_id() {
	$user = JB_Auth::current_user();
	return $user ? (int) $user->ID : 0;
}

function wp_get_current_user() {
	$user = JB_Auth::current_user();
	if ( ! $user ) {
		return new WP_User();
	}
	return $user instanceof WP_User ? $user : new WP_User( $user );
}

function get_userdata( $user_id ) {
	return JB_Auth::get_userdata( $user_id );
}

function get_user_by( $field, $value ) {
	return JB_Auth::get_user_by( $field, $value );
}

function get_users( $args = array() ) {
	return JB_Auth::get_users( $args );
}

function wp_set_current_user( $user_id ) {
	JB_Auth::set_current_user( $user_id );
}

function wp_signon( $credentials, $secure = false ) {
	return JB_Auth::signon( $credentials, $secure );
}

function wp_logout() {
	JB_Auth::logout();
}

function current_user_can( $cap, ...$args ) {
	$user_id = get_current_user_id();
	if ( ! $user_id ) {
		return false;
	}

	if ( 'edit_user' === $cap && ! empty( $args ) ) {
		if ( class_exists( 'Logistica_Roles', false ) && Logistica_Roles::user_can_manage_config( $user_id ) ) {
			return true;
		}

		return JB_Auth::user_has_cap( $user_id, 'manage_options' );
	}

	if ( class_exists( 'Logistica_Roles', false ) && 0 === strpos( $cap, 'logistica_' ) ) {
		if ( 'logistica_access' === $cap ) {
			return Logistica_Roles::user_can_access_plugin( $user_id );
		}
		if ( 'logistica_manage_config' === $cap ) {
			return Logistica_Roles::user_can_manage_config( $user_id );
		}
		if ( 0 === strpos( $cap, 'logistica_tab_' ) ) {
			$tab = substr( $cap, strlen( 'logistica_tab_' ) );
			return Logistica_Roles::user_can_tab( $tab, 'operations' );
		}
	}
	return JB_Auth::user_has_cap( $user_id, $cap );
}

function user_can( $user, $cap ) {
	$user_id = is_object( $user ) ? (int) $user->ID : (int) $user;
	if ( class_exists( 'Logistica_Roles', false ) && 'manage_options' === $cap ) {
		return JB_Auth::user_has_cap( $user_id, $cap );
	}
	if ( is_object( $user ) && isset( $user->caps[ $cap ] ) ) {
		return (bool) $user->caps[ $cap ];
	}
	return JB_Auth::user_has_cap( $user_id, $cap );
}

function get_user_meta( $user_id, $key, $single = true ) {
	global $wpdb;
	$rows = $wpdb->get_results(
		$wpdb->prepare(
			"SELECT meta_value FROM `{$wpdb->prefix}usermeta` WHERE user_id = %d AND meta_key = %s",
			$user_id,
			$key
		)
	);
	if ( empty( $rows ) ) {
		return $single ? '' : array();
	}
	$values = array_map( function ( $r ) {
		return maybe_unserialize( $r->meta_value );
	}, $rows );
	return $single ? $values[0] : $values;
}

function update_user_meta( $user_id, $key, $value ) {
	global $wpdb;
	$exists = $wpdb->get_var(
		$wpdb->prepare(
			"SELECT umeta_id FROM `{$wpdb->prefix}usermeta` WHERE user_id = %d AND meta_key = %s",
			$user_id,
			$key
		)
	);
	$data = array( 'meta_value' => maybe_serialize( $value ) );
	if ( $exists ) {
		return $wpdb->update( "{$wpdb->prefix}usermeta", $data, array( 'user_id' => $user_id, 'meta_key' => $key ) );
	}
	$data['user_id']  = $user_id;
	$data['meta_key'] = $key;
	return $wpdb->insert( "{$wpdb->prefix}usermeta", $data );
}

function delete_user_meta( $user_id, $key ) {
	global $wpdb;
	return $wpdb->delete( "{$wpdb->prefix}usermeta", array( 'user_id' => $user_id, 'meta_key' => $key ) );
}

function add_role( $role, $display_name, $capabilities = array() ) {
	foreach ( array_keys( $capabilities ) as $cap ) {
		if ( $capabilities[ $cap ] ) {
			JB_Auth::add_role_capability( $role, $cap );
		}
	}
	update_option( 'jb_role_label_' . $role, $display_name );
}

function get_role( $role ) {
	return new WP_Role( $role );
}

class WP_Role {
	public $name;
	public $capabilities = array();

	public function __construct( $role ) {
		$this->name = $role;
		$caps = JB_Auth::role_capabilities( $role );
		foreach ( $caps as $cap ) {
			$this->capabilities[ $cap ] = true;
		}
	}

	public function add_cap( $cap, $grant = true ) {
		if ( $grant ) {
			JB_Auth::add_role_capability( $this->name, $cap );
			$this->capabilities[ $cap ] = true;
		}
	}

	public function remove_cap( $cap ) {
		unset( $this->capabilities[ $cap ] );
	}
}

function wp_roles() {
	static $wp_roles = null;
	if ( null === $wp_roles ) {
		$stored = get_option( 'jb_wp_roles', array() );
		$wp_roles = (object) array(
			'roles'      => is_array( $stored ) ? $stored : array(),
			'role_names' => array(),
			'role_key'   => 'jb_wp_roles',
		);
		foreach ( $wp_roles->roles as $slug => $data ) {
			$wp_roles->role_names[ $slug ] = $data['name'] ?? $slug;
		}
	}
	return $wp_roles;
}

function wp_get_session_token() {
	return session_id() ?: '';
}
