<?php
/**
 * Lead Form Generator — Session Store
 *
 * Persistence layer for lead form flow state. Two tables:
 *
 *   {prefix}niwa_ai_lead_form_sessions — one row per visitor flow
 *   {prefix}niwa_ai_lead_form_answers  — one row per answered question
 *
 * Sessions are keyed by a client-supplied session_id (derived from the
 * existing chat session token) so we can correlate answers even before the
 * visitor reveals an email/phone.
 *
 * @package Niwa_AI
 * @since   0.12.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Niwa_AI_Lead_Form_Session_Store {

	const STATUS_ACTIVE    = 'active';
	const STATUS_PARTIAL   = 'partial';   // email/phone captured, user bailed later
	const STATUS_COMPLETED = 'completed';
	const STATUS_ABANDONED = 'abandoned';

	/**
	 * @return string Sessions table, prefixed.
	 */
	public function sessions_table() {
		global $wpdb;
		return $wpdb->prefix . Niwa_AI_Constants::TABLE_LEAD_FORM_SESSIONS;
	}

	/**
	 * @return string Answers table, prefixed.
	 */
	public function answers_table() {
		global $wpdb;
		return $wpdb->prefix . Niwa_AI_Constants::TABLE_LEAD_FORM_ANSWERS;
	}

	/**
	 * Create / update schema via dbDelta.
	 *
	 * @return void
	 */
	public function ensure_table_schema() {
		global $wpdb;

		$charset_collate = $wpdb->get_charset_collate();
		$sessions        = $this->sessions_table();
		$answers         = $this->answers_table();

		$sql_sessions = "CREATE TABLE {$sessions} (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			session_id varchar(64) NOT NULL DEFAULT '',
			mode varchar(16) NOT NULL DEFAULT 'chat',
			status varchar(16) NOT NULL DEFAULT 'active',
			started_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
			updated_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
			completed_at datetime NULL DEFAULT NULL,
			current_question_id varchar(64) NOT NULL DEFAULT '',
			contact_id bigint(20) unsigned NOT NULL DEFAULT 0,
			ip varchar(64) NOT NULL DEFAULT '',
			user_agent varchar(255) NOT NULL DEFAULT '',
			notified tinyint(1) NOT NULL DEFAULT 0,
			PRIMARY KEY  (id),
			UNIQUE KEY session_id (session_id),
			KEY status (status),
			KEY started_at (started_at)
		) {$charset_collate};";

		$sql_answers = "CREATE TABLE {$answers} (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			session_row_id bigint(20) unsigned NOT NULL,
			step_id varchar(64) NOT NULL DEFAULT '',
			question_id varchar(64) NOT NULL DEFAULT '',
			question_label varchar(500) NOT NULL DEFAULT '',
			question_type varchar(32) NOT NULL DEFAULT 'text',
			answer_text longtext NULL,
			answered_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
			PRIMARY KEY  (id),
			KEY session_row_id (session_row_id),
			KEY question_id (question_id)
		) {$charset_collate};";

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql_sessions );
		dbDelta( $sql_answers );
	}

	/**
	 * Find session row by public session id.
	 *
	 * @param string $session_id Public id.
	 * @return array|null
	 */
	public function find_by_session_id( $session_id ) {
		global $wpdb;
		$table = $this->sessions_table();
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$row = $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE session_id = %s LIMIT 1", $session_id ),
			ARRAY_A
		);
		return is_array( $row ) ? $row : null;
	}

	/**
	 * Start (or resume) a session row.
	 *
	 * @param string $session_id           Public session id (from chat).
	 * @param string $mode                 'chat'|'card'.
	 * @param string $first_question_id    Question id to seed current_question_id.
	 * @return int|null DB id of the session row, or null on failure.
	 */
	public function start_session( $session_id, $mode, $first_question_id ) {
		global $wpdb;

		$existing = $this->find_by_session_id( $session_id );
		if ( $existing ) {
			return (int) $existing['id'];
		}

		$table = $this->sessions_table();
		$ok    = $wpdb->insert(
			$table,
			array(
				'session_id'          => substr( (string) $session_id, 0, 64 ),
				'mode'                => in_array( $mode, array( 'chat', 'card' ), true ) ? $mode : 'chat',
				'status'              => self::STATUS_ACTIVE,
				'current_question_id' => substr( (string) $first_question_id, 0, 64 ),
				'ip'                  => substr( $this->client_ip(), 0, 64 ),
				'user_agent'          => isset( $_SERVER['HTTP_USER_AGENT'] ) ? substr( sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_USER_AGENT'] ) ), 0, 255 ) : '',
				'started_at'          => current_time( 'mysql' ),
				'updated_at'          => current_time( 'mysql' ),
			),
			array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
		);

		if ( false === $ok ) {
			return null;
		}
		return (int) $wpdb->insert_id;
	}

	/**
	 * Record an answer.
	 *
	 * @param int    $session_row_id Sessions table id.
	 * @param array  $question       Question schema record.
	 * @param string $step_id        Step id.
	 * @param mixed  $answer         Raw answer (scalar or array for multi).
	 * @return int|null Answer row id.
	 */
	public function record_answer( $session_row_id, array $question, $step_id, $answer ) {
		global $wpdb;
		$table = $this->answers_table();

		$answer_text = is_array( $answer )
			? wp_json_encode( array_map( 'sanitize_text_field', array_map( 'strval', $answer ) ) )
			: sanitize_textarea_field( (string) $answer );

		$ok = $wpdb->insert(
			$table,
			array(
				'session_row_id' => (int) $session_row_id,
				'step_id'        => substr( (string) $step_id, 0, 64 ),
				'question_id'    => substr( isset( $question['id'] ) ? (string) $question['id'] : '', 0, 64 ),
				'question_label' => substr( isset( $question['label'] ) ? (string) $question['label'] : '', 0, 500 ),
				'question_type'  => substr( isset( $question['type'] ) ? (string) $question['type'] : 'text', 0, 32 ),
				'answer_text'    => $answer_text,
				'answered_at'    => current_time( 'mysql' ),
			),
			array( '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
		);

		if ( false === $ok ) {
			return null;
		}
		return (int) $wpdb->insert_id;
	}

	/**
	 * Update session progress pointer.
	 *
	 * @param int    $session_row_id DB id.
	 * @param string $next_qid       Next question id or '' if complete.
	 * @param string $status         New status.
	 * @return void
	 */
	public function update_progress( $session_row_id, $next_qid, $status ) {
		global $wpdb;
		$table = $this->sessions_table();

		$data = array(
			'current_question_id' => substr( (string) $next_qid, 0, 64 ),
			'status'              => in_array( $status, array(
				self::STATUS_ACTIVE,
				self::STATUS_PARTIAL,
				self::STATUS_COMPLETED,
				self::STATUS_ABANDONED,
			), true ) ? $status : self::STATUS_ACTIVE,
			'updated_at'          => current_time( 'mysql' ),
		);

		if ( self::STATUS_COMPLETED === $data['status'] ) {
			$data['completed_at'] = current_time( 'mysql' );
		}

		$wpdb->update( $table, $data, array( 'id' => (int) $session_row_id ), array( '%s', '%s', '%s', '%s' ), array( '%d' ) );
	}

	/**
	 * Attach a contact row to the session.
	 *
	 * @param int $session_row_id DB id.
	 * @param int $contact_id     Contact id from Contact_Service.
	 * @return void
	 */
	public function attach_contact( $session_row_id, $contact_id ) {
		global $wpdb;
		$wpdb->update(
			$this->sessions_table(),
			array(
				'contact_id' => (int) $contact_id,
				'updated_at' => current_time( 'mysql' ),
			),
			array( 'id' => (int) $session_row_id ),
			array( '%d', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Mark session as notified (email sent).
	 *
	 * @param int $session_row_id DB id.
	 * @return void
	 */
	public function mark_notified( $session_row_id ) {
		global $wpdb;
		$wpdb->update(
			$this->sessions_table(),
			array( 'notified' => 1, 'updated_at' => current_time( 'mysql' ) ),
			array( 'id' => (int) $session_row_id ),
			array( '%d', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Get all answers for a session in order.
	 *
	 * @param int $session_row_id DB id.
	 * @return array<int,array>
	 */
	public function get_answers( $session_row_id ) {
		global $wpdb;
		$table = $this->answers_table();
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$rows = $wpdb->get_results(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE session_row_id = %d ORDER BY id ASC", (int) $session_row_id ),
			ARRAY_A
		);
		return is_array( $rows ) ? $rows : array();
	}

	/**
	 * Derive best-effort client IP.
	 *
	 * @return string
	 */
	private function client_ip() {
		return Niwa_AI_Session_Utils::get_client_ip();
	}
}
