<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/* =====================================================================
   HELPER DEVICE AUTO-LOGIN  (versi KOMPATIBEL semua versi PHP)
   - Tidak memakai random_int()/random_bytes() langsung (butuh PHP 7+)
   - Pakai openssl_random_pseudo_bytes() / mt_rand() sebagai pengganti
   - Fitur: token hash, rotasi 22:30, grace period, recovery, kode 10 char
===================================================================== */

/* ---------- sumber keacakan yang aman & kompatibel PHP 5.3+ ---------- */
if (!function_exists('az_secure_bytes')) {
	function az_secure_bytes($len) {
		$len = (int)$len;
		if ($len < 1) $len = 1;
		if (function_exists('random_bytes')) {
			return random_bytes($len);                 // PHP 7+
		}
		if (function_exists('openssl_random_pseudo_bytes')) {
			return openssl_random_pseudo_bytes($len);  // PHP 5.3+
		}
		$out = '';
		for ($i = 0; $i < $len; $i++) $out .= chr(mt_rand(0, 255)); // fallback
		return $out;
	}
}

if (!function_exists('az_secure_int')) {
	// integer acak dalam [$min,$max]; aman & benar untuk range <= 256
	function az_secure_int($min, $max) {
		$min = (int)$min; $max = (int)$max;
		if ($max < $min) { $t = $min; $min = $max; $max = $t; }
		if (function_exists('random_int')) {
			return random_int($min, $max);             // PHP 7+
		}
		$range = $max - $min + 1;
		if ($range <= 1) return $min;
		if (function_exists('openssl_random_pseudo_bytes')) {
			$limit = 256 - (256 % $range);             // rejection sampling
			do { $r = ord(openssl_random_pseudo_bytes(1)); } while ($r >= $limit);
			return $min + ($r % $range);
		}
		$gmax = mt_getrandmax();
		$limit = $gmax - ($gmax % $range);
		do { $r = mt_rand(); } while ($r >= $limit);
		return $min + ($r % $range);
	}
}

/* ---------- token & hash ---------- */
if (!function_exists('az_device_secret')) {
	function az_device_secret() {
		return bin2hex(az_secure_bytes(32));
	}
}

if (!function_exists('az_device_hash')) {
	function az_device_hash($value) {
		return hash('sha256', (string)$value);
	}
}

if (!function_exists('az_next_rotation_time')) {
	function az_next_rotation_time($now = null) {
		$now = $now ? $now : time();
		$today = strtotime(date('Y-m-d', $now) . ' 22:30:00');
		if ($today > $now) return date('Y-m-d H:i:s', $today);
		return date('Y-m-d H:i:s', strtotime('+1 day', $today));
	}
}

/* ---------- baca / tulis cookie device ---------- */
if (!function_exists('az_get_device_token')) {
	function az_get_device_token() {
		$ci =& get_instance();
		$t = '';
		if (method_exists($ci->input, 'cookie')) $t = $ci->input->cookie('az_device_token');
		if (strlen($t) == 0 && isset($_COOKIE['az_device_token'])) $t = $_COOKIE['az_device_token'];
		if (strlen($t) == 0) {
			foreach ($_COOKIE as $k => $v) {
				if (substr($k, -15) === 'az_device_token') { $t = $v; break; }
			}
		}
		return is_string($t) ? $t : '';
	}
}

if (!function_exists('az_set_device_cookie')) {
	function az_set_device_cookie($token) {
		$ci =& get_instance();
		if (!headers_sent()) {
			setcookie('az_device_token', $token, time() + 31536000, '/', '', false, true);
		}
		if (isset($ci->input) && method_exists($ci->input, 'set_cookie')) {
			$ci->input->set_cookie('az_device_token', $token, 31536000, '', '/', '', FALSE, TRUE);
		}
	}
}

if (!function_exists('az_get_device_type')) {
	function az_get_device_type() {
		$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
		return preg_match('/Mobile|Android|iPhone|iPad|iPod/i', $ua) ? 'HP' : 'PC';
	}
}

/* ---------- KODE AKTIVASI 10 KARAKTER (aman, tanpa random_int) ---------- */
if (!function_exists('az_shuffle_secure')) {
	function az_shuffle_secure($str) {
		$arr = str_split($str);
		for ($i = count($arr) - 1; $i > 0; $i--) {
			$j = az_secure_int(0, $i);
			$tmp = $arr[$i]; $arr[$i] = $arr[$j]; $arr[$j] = $tmp;
		}
		return implode('', $arr);
	}
}

if (!function_exists('az_generate_activation_code')) {
	function az_generate_activation_code($len = 10) {
		// karakter ambigu (0,O,o,I,i,l,1) dibuang biar tidak salah ketik
		$upper   = 'ABCDEFGHJKMNPQRSTUVWXYZ';
		$lower   = 'abcdefghjkmnpqrstuvwxyz';
		$digits  = '23456789';
		$symbols = '@#%&*?';
		$pool    = $upper . $lower . $digits . $symbols;
		// jaminan minimal 1 dari tiap kelas
		$code  = $upper[az_secure_int(0, strlen($upper) - 1)];
		$code .= $lower[az_secure_int(0, strlen($lower) - 1)];
		$code .= $digits[az_secure_int(0, strlen($digits) - 1)];
		$code .= $symbols[az_secure_int(0, strlen($symbols) - 1)];
		for ($i = 4; $i < $len; $i++) {
			$code .= $pool[az_secure_int(0, strlen($pool) - 1)];
		}
		return az_shuffle_secure($code);
	}
}

/* ---------- rotasi / refresh token ---------- */
if (!function_exists('az_refresh_device_token')) {
	function az_refresh_device_token($device, $old_hash) {
		$ci =& get_instance(); $ci->load->database();
		$new_token = az_device_secret();
		$new_hash  = az_device_hash($new_token);
		$old_expires = date('Y-m-d H:i:s', strtotime('+10 minutes'));
		if (!empty($device->previous_token_hash)
			&& $device->previous_token_hash === $old_hash
			&& !empty($device->previous_token_expires)) {
			if (strtotime($device->previous_token_expires) < strtotime($old_expires)) {
				$old_expires = $device->previous_token_expires;
			}
		}
		$ci->db->where('iddevice', $device->iddevice);
		$ci->db->update('device_registry', array(
			'device_token_hash'      => $new_hash,
			'previous_token_hash'    => $old_hash,
			'previous_token_expires' => $old_expires,
			'token_rotated_at'       => date('Y-m-d H:i:s'),
			'next_rotation_at'       => az_next_rotation_time(),
		));
		az_set_device_cookie($new_token);
		$device->device_token_hash = $new_hash;
		return $device;
	}
}

if (!function_exists('az_check_device')) {
	function az_check_device() {
		$ci =& get_instance(); $ci->load->database();
		$token = az_get_device_token();
		if (strlen($token) == 0) return array('allowed' => false, 'device' => null, 'token_state' => 'missing');
		$hash = az_device_hash($token);
		$now  = date('Y-m-d H:i:s');
		$ci->db->where('status', 1);
		$ci->db->group_start();
			$ci->db->where('device_token_hash', $hash);
			$ci->db->or_group_start();
				$ci->db->where('previous_token_hash', $hash);
				$ci->db->where('previous_token_expires >=', $now);
			$ci->db->group_end();
		$ci->db->group_end();
		$device = $ci->db->get('device_registry')->row();
		if (!$device) return array('allowed' => false, 'device' => null, 'token_state' => 'invalid');
		$is_previous = empty($device->device_token_hash) || $device->device_token_hash !== $hash;
		$need_rotate = $is_previous
			|| empty($device->next_rotation_at)
			|| strtotime($device->next_rotation_at) <= strtotime($now);
		if ($need_rotate) $device = az_refresh_device_token($device, $hash);
		return array('allowed' => true, 'device' => $device, 'token_state' => $is_previous ? 'grace' : 'active');
	}
}

if (!function_exists('az_issue_device_tokens')) {
	function az_issue_device_tokens($iddevice, $rotate_recovery = true) {
		$ci =& get_instance(); $ci->load->database();
		$device_token   = az_device_secret();
		$recovery_token = az_device_secret();
		$update = array(
			'device_token_hash'      => az_device_hash($device_token),
			'previous_token_hash'    => null,
			'previous_token_expires' => null,
			'token_rotated_at'       => date('Y-m-d H:i:s'),
			'next_rotation_at'       => az_next_rotation_time(),
		);
		if ($rotate_recovery) {
			$update['recovery_token_hash']   = az_device_hash($recovery_token);
			$update['recovery_token_status'] = 'active';
		}
		$ci->db->where('iddevice', $iddevice);
		$ci->db->update('device_registry', $update);
		az_set_device_cookie($device_token);
		return $rotate_recovery ? $recovery_token : null;
	}
}

if (!function_exists('az_rotate_all_device_tokens')) {
	function az_rotate_all_device_tokens($grace_hours = 6) {
		$ci =& get_instance(); $ci->load->database();
		$now  = date('Y-m-d H:i:s');
		$next = az_next_rotation_time();
		$ci->db->where('status', 1);
		$ci->db->where('device_token_hash IS NOT NULL', null, false);
		$ci->db->where('next_rotation_at <=', $now);
		$devices = $ci->db->get('device_registry')->result();
		foreach ($devices as $d) {
			$ci->db->where('iddevice', $d->iddevice);
			$ci->db->update('device_registry', array(
				'previous_token_hash'    => $d->device_token_hash,
				'previous_token_expires' => date('Y-m-d H:i:s', strtotime('+' . $grace_hours . ' hours')),
				'device_token_hash'      => null,
				'token_rotated_at'       => $now,
				'next_rotation_at'       => $next,
			));
		}
		return count($devices);
	}
}

/* ---------- mapping user <-> device ---------- */
if (!function_exists('az_get_device_users')) {
	function az_get_device_users($iddevice) {
		$ci =& get_instance();
		$ci->db->select('user.iduser, user.name, user.username');
		$ci->db->from('device_user');
		$ci->db->join('user', 'user.iduser = device_user.iduser', 'left');
		$ci->db->where('device_user.iddevice', $iddevice);
		$ci->db->where('device_user.status', 1);
		$ci->db->where('user.status', 1);
		$ci->db->order_by('user.name');
		return $ci->db->get()->result();
	}
}

if (!function_exists('az_save_device_users')) {
	function az_save_device_users($iddevice, $arr_iduser) {
		$ci =& get_instance();
		$ci->db->where('iddevice', $iddevice);
		$ci->db->update('device_user', array('status' => 0));
		if (is_array($arr_iduser)) {
			foreach ($arr_iduser as $uid) {
				$uid = intval($uid);
				if ($uid > 0) {
					$ci->db->where('iddevice', $iddevice);
					$ci->db->where('iduser', $uid);
					$exist = $ci->db->get('device_user')->row();
					if ($exist) {
						$ci->db->where('id', $exist->id);
						$ci->db->update('device_user', array('status' => 1));
					} else {
						$ci->db->insert('device_user', array('iddevice' => $iddevice, 'iduser' => $uid, 'status' => 1));
					}
				}
			}
		}
	}
}