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

class Login_model extends MY_Model
{
	private const USERS = 'tbl_users';
	private const DETAILS = 'tbl_account_details';

	public function rules(): array
	{
		return array(
			array(
				'field' => 'user_name',
				'label' => 'Username',
				'rules' => 'trim|required'
			),
			array(
				'field' => 'password',
				'label' => 'Password',
				'rules' => 'trim|required'
			),
		);
	}

	public function attempt(string $username, string $password, bool $remember = false): bool
	{
		$this->load->helper('cookie');
		$currentHash = $this->hash($password);
		$passwordHashes = array_unique(array_merge(array($currentHash), $this->legacy_password_hashes($password)));

		$user = $this->db
			->where('activated', 1)
			->where('banned', 0)
			->group_start()
			->where('LOWER(username)=', strtolower($username))
			->or_where('LOWER(email)=', strtolower($username))
			->group_end()
			->get(self::USERS)
			->result();
		if (!$user) {
			return false;
		}

		$matchedUser = null;
		foreach ($user as $candidate) {
			$storedPassword = (string) $candidate->password;
			if (in_array($storedPassword, $passwordHashes, true) || password_verify($password, $storedPassword)) {
				$matchedUser = $candidate;
				break;
			}
		}

		if (!$matchedUser) {
			return false;
		}

		$user = $matchedUser;

		if (!hash_equals((string) $user->password, $currentHash)) {
			$this->db->where('user_id', $user->user_id)->update(self::USERS, array('password' => $currentHash));
			$user->password = $currentHash;
		}

		$details = $this->db
			->where('user_id', $user->user_id)
			->get(self::DETAILS)
			->row();

		$clientId = 0;
		if ((int) $user->role_id === 2) {
			$clientId = (int) ($details->company ?? 0);
			if (!$clientId) {
				$client = $this->db->select('client_id')->where('primary_contact', (int) $user->user_id)->get('tbl_client')->row();
				$clientId = (int) ($client->client_id ?? 0);
			}
		}

		$direction = $details->direction ?? (config_item('RTL') ? 'rtl' : 'ltr');
		$targetUrl = $this->session->userdata('url') ?: ($user->role_id == 2 ? 'client/dashboard' : 'admin/dashboard');

		$sessionData = array(
			'user_id' => $user->user_id,
			'user_name' => $user->username,
			'email' => $user->email,
			'name' => $details->fullname ?? $user->username,
			'photo' => $details->avatar ?? null,
			'user_type' => $user->role_id,
			'user_flag' => $user->role_id,
			'direction' => $direction,
			'designations_id' => $details->designations_id ?? null,
			'warehouse_id' => $details->warehouse_id ?? null,
			'client_id' => $clientId,
			'url' => $targetUrl,
			'loggedin' => true,
			'online_time' => time(),
			'remember' => $remember,
		);

		$this->session->set_userdata($sessionData);
		// Avoid forcing regeneration here; let CI handle it via sess_time_to_update

		$this->db->where('user_id', $user->user_id)->update(self::USERS, array('last_login' => date('Y-m-d H:i:s')));

		if ($remember) {
			$token = bin2hex(random_bytes(32));
			$hashed = hash('sha256', $token);
			$expire = time() + (60 * 60 * 24 * 30); // 30 days
			$this->db->where('user_id', $user->user_id)->update(self::USERS, array(
				'remember_token' => $hashed,
				'remember_expire' => date('Y-m-d H:i:s', $expire),
			));
			$this->input->set_cookie(array(
				'name' => 'remember_token',
				'value' => $user->user_id . ':' . $token,
				'expire' => $expire,
				'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
				'httponly' => true,
				'path' => '/',
				'samesite' => 'Lax',
			));
		}

		return true;
	}

	public function register_client_company(array $payload): array
	{
		$email = strtolower(trim((string) ($payload['email'] ?? '')));
		$username = trim((string) ($payload['username'] ?? ''));
		$password = (string) ($payload['password'] ?? '');
		$companyName = trim((string) ($payload['company_name'] ?? ''));
		$fullName = trim((string) ($payload['fullname'] ?? ''));

		if ($email === '' || $username === '' || $password === '' || $companyName === '' || $fullName === '') {
			return array('success' => false, 'message' => 'Please complete all required fields.');
		}

		$existingUser = $this->db
			->group_start()
			->where('LOWER(username)=', strtolower($username))
			->or_where('LOWER(email)=', $email)
			->group_end()
			->get(self::USERS)
			->row();
		if ($existingUser) {
			return array('success' => false, 'message' => 'Username or email already exists.');
		}

		$existingClient = $this->db->where('LOWER(email)=', $email)->get('tbl_client')->row();
		if ($existingClient) {
			return array('success' => false, 'message' => 'A client company already exists for this email.');
		}

		$this->db->trans_start();

		$this->db->insert('tbl_client', array(
			'primary_contact' => 0,
			'name' => $companyName,
			'email' => $email,
			'website' => trim((string) ($payload['website'] ?? '')),
			'phone' => trim((string) ($payload['phone'] ?? '')),
			'mobile' => trim((string) ($payload['phone'] ?? '')),
			'address' => trim((string) ($payload['address'] ?? '')),
			'city' => trim((string) ($payload['city'] ?? '')),
			'country' => trim((string) ($payload['country'] ?? '')),
			'currency' => config_item('default_currency') ?: 'USD',
			'language' => config_item('language') ?: 'english',
			'username' => $username,
			'password' => encrypt($password),
			'client_status' => 1,
			'active' => '1',
			'sms_notification' => 0,
			'permission' => 'all',
		));
		$clientId = (int) $this->db->insert_id();

		$this->db->insert(self::USERS, array(
			'username' => $username,
			'email' => $email,
			'password' => $this->hash($password),
			'role_id' => 2,
			'activated' => 1,
			'banned' => 0,
			'created' => date('Y-m-d H:i:s'),
			'online_time' => 0,
			'permission' => 'all',
		));
		$userId = (int) $this->db->insert_id();

		$this->db->insert(self::DETAILS, array(
			'user_id' => $userId,
			'fullname' => $fullName,
			'company' => $clientId,
			'city' => trim((string) ($payload['city'] ?? '')),
			'country' => trim((string) ($payload['country'] ?? '')),
			'locale' => 'en_US',
			'address' => trim((string) ($payload['address'] ?? '')),
			'phone' => trim((string) ($payload['phone'] ?? '')),
			'mobile' => trim((string) ($payload['phone'] ?? '')),
			'language' => config_item('language') ?: 'english',
			'designations_id' => 0,
			'avatar' => 'uploads/default_avatar.jpg',
			'joining_date' => date('Y-m-d'),
			'direction' => config_item('RTL') ? 'rtl' : 'ltr',
		));

		$this->db->where('client_id', $clientId)->update('tbl_client', array('primary_contact' => $userId));

		$this->db->trans_complete();

		if (!$this->db->trans_status()) {
			return array('success' => false, 'message' => 'Unable to create account. Please try again.');
		}

		return array('success' => true, 'client_id' => $clientId, 'user_id' => $userId);
	}

	public function request_password_reset(string $identity): array
	{
		$identity = strtolower(trim($identity));
		if ($identity === '') {
			return array('email_sent' => true);
		}

		$user = $this->db
			->where('activated', 1)
			->where('banned', 0)
			->group_start()
			->where('LOWER(username)=', $identity)
			->or_where('LOWER(email)=', $identity)
			->group_end()
			->get(self::USERS)
			->row();

		if (!$user) {
			return array('email_sent' => true);
		}

		$key = bin2hex(random_bytes(20));
		$this->db->where('user_id', (int) $user->user_id)->update(self::USERS, array(
			'new_password_key' => $key,
			'new_password_requested' => date('Y-m-d H:i:s'),
		));

		$sent = $this->send_password_reset_email($user, $key);
		if (!$sent) {
			return array('email_sent' => false, 'message' => 'Unable to send password reset instructions. Please check email settings.');
		}

		return array('email_sent' => true);
	}

	public function get_valid_password_reset(int $userId, string $key)
	{
		if ($userId <= 0 || $key === '') {
			return null;
		}

		$user = $this->db
			->where('user_id', $userId)
			->where('activated', 1)
			->where('banned', 0)
			->get(self::USERS)
			->row();

		if (!$user || empty($user->new_password_key) || !hash_equals((string) $user->new_password_key, $key)) {
			return null;
		}

		$requested = !empty($user->new_password_requested) ? strtotime((string) $user->new_password_requested) : false;
		$expires = (int) (config_item('forgot_password_expire') ?: 86400);
		if (!$requested || ($requested + $expires) < time()) {
			return null;
		}

		return $user;
	}

	public function complete_password_reset(int $userId, string $key, string $password): bool
	{
		$user = $this->get_valid_password_reset($userId, $key);
		if (!$user) {
			return false;
		}

		$this->db->where('user_id', $userId)->update(self::USERS, array(
			'password' => $this->hash($password),
			'new_password_key' => null,
			'new_password_requested' => null,
			'remember_token' => null,
			'remember_expire' => null,
			'modified' => date('Y-m-d H:i:s'),
		));

		return $this->db->affected_rows() >= 0;
	}

	private function send_password_reset_email($user, string $key): bool
	{
		$resetUrl = site_url('login/reset_password/' . (int) $user->user_id . '/' . rawurlencode($key));
		$template = function_exists('email_templates') ? email_templates(array('email_group' => 'forgot_password'), (int) $user->user_id, true) : null;
		$siteName = config_item('company_name') ?: config_item('website_name');

		if ($template && !empty($template->template_body)) {
			$message = str_replace(
				array('{SITE_NAME}', '{PASS_KEY_URL}', '{USERNAME}', '{EMAIL}'),
				array($siteName, $resetUrl, $user->username, $user->email),
				$template->template_body
			);
			$subject = !empty($template->subject) ? $template->subject : 'Reset your password';
		} else {
			$subject = 'Reset your password';
			$message = '<p>Hello ' . html_escape($user->username) . ',</p>'
				. '<p>Use the link below to reset your password. This link expires in 24 hours.</p>'
				. '<p><a href="' . html_escape($resetUrl) . '">' . html_escape($resetUrl) . '</a></p>'
				. '<p>If you did not request this, you can ignore this email.</p>';
		}

		$params = array(
			'recipient' => $user->email,
			'subject' => '[ ' . $siteName . ' ] ' . $subject,
			'message' => $message,
			'resourceed_file' => '',
		);

		$warnings = array();
		set_error_handler(function ($severity, $message, $file, $line) use (&$warnings) {
			if (!(error_reporting() & $severity)) {
				return false;
			}
			$warnings[] = $message . ' in ' . $file . ':' . $line;
			return true;
		});

		ob_start();
		try {
			$result = $this->send_email($params);
			$sent = ($result !== false);
		} catch (Throwable $exception) {
			$warnings[] = $exception->getMessage();
			$sent = function_exists('send_later') ? (bool) send_later($params) : false;
		}
		$output = ob_get_clean();
		restore_error_handler();

		if (!empty($warnings) || trim((string) $output) !== '') {
			log_message('error', 'Password reset email warning: ' . implode(' | ', $warnings) . ' ' . trim((string) $output));
		}

		return $sent;
	}

	private function legacy_password_hashes(string $password): array
	{
		$keys = config_item('legacy_password_keys');
		if (empty($keys)) {
			return array();
		}
		if (!is_array($keys)) {
			$keys = array($keys);
		}

		$hashes = array();
		foreach ($keys as $key) {
			if ($key !== '' && $key !== config_item('encryption_key')) {
				$hashes[] = hash('sha512', $password . $key);
			}
		}

		return $hashes;
	}

	public function logged_in(): bool
	{
		// echo '<pre>';
		// print_r($this->session->userdata());
		// echo '</pre>';
		// exit();
		// check session first
		if ($this->session->userdata('loggedin')) {
			return true;
		}
		// then check remember-me cookie
		return $this->resume_from_remember();
	}

	/**
	 * Restore session from remember-me cookie if valid.
	 */
	public function resume_from_remember(): bool
	{
		$this->load->helper('cookie');
		$cookie = $this->input->cookie('remember_token');
		if (!$cookie || strpos($cookie, ':') === false) {
			return false;
		}

		list($userId, $token) = explode(':', $cookie, 2);
		if (empty($userId) || empty($token)) {
			return false;
		}

		$user = $this->db->where('user_id', (int) $userId)->get(self::USERS)->row();
		if (!$user || empty($user->remember_token) || empty($user->remember_expire)) {
			return false;
		}

		if (safe_strtotime($user->remember_expire) < time()) {
			return false;
		}

		if (!hash_equals($user->remember_token, hash('sha256', $token))) {
			return false;
		}

		// Build session payload from stored user data.
		$details = $this->db
			->where('user_id', $user->user_id)
			->get(self::DETAILS)
			->row();

		$clientId = 0;
		if ((int) $user->role_id === 2) {
			$clientId = (int) ($details->company ?? 0);
			if (!$clientId) {
				$client = $this->db->select('client_id')->where('primary_contact', (int) $user->user_id)->get('tbl_client')->row();
				$clientId = (int) ($client->client_id ?? 0);
			}
		}

		$direction = $details->direction ?? (config_item('RTL') ? 'rtl' : 'ltr');
		$targetUrl = $this->session->userdata('url') ?: ($user->role_id == 2 ? 'client/dashboard' : 'admin/dashboard');

		$sessionData = array(
			'user_id' => $user->user_id,
			'user_name' => $user->username,
			'email' => $user->email,
			'name' => $details->fullname ?? $user->username,
			'photo' => $details->avatar ?? null,
			'user_type' => $user->role_id,
			'user_flag' => $user->role_id,
			'direction' => $direction,
			'designations_id' => $details->designations_id ?? null,
			'warehouse_id' => $details->warehouse_id ?? null,
			'client_id' => $clientId,
			'url' => $targetUrl,
			'loggedin' => true,
			'online_time' => time(),
			'remember' => true,
		);

		$this->session->set_userdata($sessionData);
		// Avoid forcing regeneration here; let CI handle it via sess_time_to_update

		return true;
	}

	public function logout(): void
	{
		$this->load->helper('cookie');
		$cookie = $this->input->cookie('remember_token');
		if ($cookie && strpos($cookie, ':') !== false) {
			list($userId) = explode(':', $cookie, 2);
			if (!empty($userId)) {
				$this->db->where('user_id', (int) $userId)->update(self::USERS, array(
					'remember_token' => null,
					'remember_expire' => null,
				));
			}
		}
		delete_cookie('remember_token');
		$userId = $this->session->userdata('user_id');
		if ($userId) {
			$this->db->where('user_id', $userId)->update(self::USERS, array('last_login' => date('Y-m-d H:i:s')));
		}
		$this->session->sess_destroy();
	}
}
