<?php
/**
 * Answyr — Settings store + admin page.
 *
 * Thin wrapper over the options table. API keys are stored per-provider and
 * encrypted at rest (see Answyr_Crypto), so several providers can be
 * configured and the active one switched in settings.
 *
 * @package Answyr
 */

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

final class Answyr_Settings {

	private const PREFIX = 'answyr_';

	/** Generic option get with default. */
	public static function get( string $key, $fallback = null ) {
		return get_option( self::PREFIX . $key, $fallback );
	}

	public static function set( string $key, $value ): void {
		update_option( self::PREFIX . $key, $value );
	}

	/** Decrypted per-provider API key. */
	public static function api_key( string $provider_id ): string {
		$stored = (string) get_option( self::PREFIX . 'api_key_' . sanitize_key( $provider_id ), '' );
		return '' === $stored ? '' : Answyr_Crypto::decrypt( $stored );
	}

	/** Store a per-provider API key (encrypted). Empty string clears it. */
	public static function set_api_key( string $provider_id, string $plaintext ): void {
		$option = self::PREFIX . 'api_key_' . sanitize_key( $provider_id );
		if ( '' === trim( $plaintext ) ) {
			delete_option( $option );
			return;
		}
		update_option( $option, Answyr_Crypto::encrypt( $plaintext ), false );
	}

	/**
	 * Inline `style` attribute that overrides the accent CSS vars from the
	 * configured brand color. Apply to a public-surface wrapper (portal, login,
	 * intake form, chat) so every descendant — including JS-built buttons and
	 * tabs — themes off it. wp-admin surfaces are left native on purpose.
	 *
	 * Returns '' when the color is the token default, so we don't emit redundant
	 * inline styles.
	 */
	public static function accent_style(): string {
		$hex = sanitize_hex_color( (string) self::get( 'chat_color', '#2271b1' ) );
		if ( ! $hex || '#2271b1' === strtolower( $hex ) ) {
			return '';
		}
		list( $r, $g, $b ) = self::hex_to_rgb( $hex );
		// Hover: ~15% darker. Soft: a faint accent tint for focus glows.
		$hover = sprintf( '#%02x%02x%02x', (int) ( $r * 0.85 ), (int) ( $g * 0.85 ), (int) ( $b * 0.85 ) );
		$soft  = sprintf( 'rgba(%d, %d, %d, 0.12)', $r, $g, $b );
		return sprintf(
			'style="--answyr-accent:%s;--answyr-accent-hover:%s;--answyr-accent-soft:%s"',
			esc_attr( $hex ),
			esc_attr( $hover ),
			esc_attr( $soft )
		);
	}

	/** Split a #rgb or #rrggbb hex into an [r, g, b] int triple. */
	private static function hex_to_rgb( string $hex ): array {
		$hex = ltrim( $hex, '#' );
		if ( 3 === strlen( $hex ) ) {
			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
		}
		return array(
			(int) hexdec( substr( $hex, 0, 2 ) ),
			(int) hexdec( substr( $hex, 2, 2 ) ),
			(int) hexdec( substr( $hex, 4, 2 ) ),
		);
	}

	/** Whether the public AI chat endpoint requires a logged-in user. */
	public static function chat_requires_login(): bool {
		return (bool) self::get( 'chat_require_login', false );
	}

	/** Site-wide daily ceiling on AI chat requests (0 = unlimited). Guards the owner's API spend. */
	public static function chat_daily_cap(): int {
		return max( 0, (int) self::get( 'chat_daily_cap', 500 ) );
	}

	/** Is a surface module enabled? Desk on by default; Chat off until configured. */
	public static function module_enabled( string $module_id ): bool {
		$defaults = array(
			'desk' => true,
			'chat' => false,
		);
		return (bool) get_option( self::PREFIX . 'module_' . $module_id, $defaults[ $module_id ] ?? false );
	}

	/** Seed defaults on activation. */
	public static function seed_defaults(): void {
		add_option( self::PREFIX . 'provider', 'anthropic' );
		add_option( self::PREFIX . 'model', 'claude-sonnet-4-6' );
		add_option( self::PREFIX . 'max_tokens', 1500 );
		add_option( self::PREFIX . 'temperature', 0.3 );
		add_option( self::PREFIX . 'module_desk', true );
		add_option( self::PREFIX . 'module_chat', false );
		// Flush rewrites once on next load so KB article URLs resolve.
		update_option( 'answyr_flush_kb', '1' );
		add_option( self::PREFIX . 'agent_signature', '' );
		add_option( self::PREFIX . 'excluded_post_types', array() );
		add_option( self::PREFIX . 'notify_email', get_option( 'admin_email' ) );
		add_option( self::PREFIX . 'portal_url', '' );
		add_option( self::PREFIX . 'canned', array() );
		add_option( self::PREFIX . 'chat_title', __( 'Ask a question', 'answyr' ) );
		add_option( self::PREFIX . 'chat_welcome', __( 'Hi! I can answer questions from our knowledge base. How can I help you?', 'answyr' ) );
		add_option( self::PREFIX . 'chat_placeholder', __( 'Type your question…', 'answyr' ) );
		add_option( self::PREFIX . 'chat_color', '#2271b1' );
		add_option( self::PREFIX . 'chat_display_mode', 'sitewide' );
		add_option( self::PREFIX . 'chat_require_login', 0 );
		add_option( self::PREFIX . 'chat_daily_cap', 500 );
	}

	// ── Admin ───────────────────────────────────────────────────────────────────

	/** Hooks for the settings screen — called from the plugin orchestrator. */
	public static function init_admin(): void {
		add_action( 'admin_post_answyr_save_settings', array( __CLASS__, 'handle_save' ) );
		add_action( 'admin_post_answyr_create_page', array( __CLASS__, 'handle_create_page' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
	}

	/** Shortcode → default page title for the one-click customer pages. */
	private const PAGE_TYPES = array(
		'portal' => '[answyr_portal]',
		'form'   => '[answyr_form]',
		'kb'     => '[answyr_kb]',
	);

	/** The published page ID that embeds a given shortcode, or 0 if none. */
	public static function find_page_with_shortcode( string $shortcode ): int {
		global $wpdb;
		$like = '%' . $wpdb->esc_like( '[' . trim( $shortcode, '[]' ) ) . '%';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Admin-only settings screen; one-off lookup, not worth a cache invalidated on every edit.
		$id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_content LIKE %s LIMIT 1",
				$like
			)
		);
		return (int) $id;
	}

	/**
	 * One-click "create a customer page" from Settings: publishes a page holding
	 * the portal or support-form shortcode (reusing one if it already exists), and
	 * points the Portal URL setting at the portal page.
	 */
	public static function handle_create_page(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'answyr' ) );
		}
		check_admin_referer( 'answyr_create_page' );

		$type = sanitize_key( wp_unslash( $_POST['page_type'] ?? '' ) );
		if ( ! isset( self::PAGE_TYPES[ $type ] ) ) {
			wp_die( esc_html__( 'Unknown page type.', 'answyr' ) );
		}
		$shortcode = self::PAGE_TYPES[ $type ];

		$existing = self::find_page_with_shortcode( $shortcode );
		if ( $existing ) {
			$page_id = $existing;
			$result  = 'exists';
		} else {
			$titles  = array(
				'portal' => __( 'Support Portal', 'answyr' ),
				'form'   => __( 'Contact Support', 'answyr' ),
				'kb'     => __( 'Knowledge Base', 'answyr' ),
			);
			$page_id = wp_insert_post(
				array(
					'post_title'   => $titles[ $type ],
					'post_content' => $shortcode,
					'post_status'  => 'publish',
					'post_type'    => 'page',
				)
			);
			$result  = ( is_wp_error( $page_id ) || ! $page_id ) ? 'error' : 'created';
		}

		if ( 'portal' === $type && 'error' !== $result ) {
			self::set( 'portal_url', (string) get_permalink( $page_id ) );
		}

		wp_safe_redirect(
			add_query_arg(
				array(
					'page'         => 'answyr-settings',
					'page_created' => $result,
					'page_type'    => $type,
					'page_id'      => is_wp_error( $page_id ) ? 0 : (int) $page_id,
				),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	public static function enqueue( string $hook ): void {
		if ( 'answyr_page_answyr-settings' !== $hook ) {
			return;
		}
		wp_enqueue_script(
			'answyr-settings',
			ANSWYR_URL . 'assets/admin-settings.js',
			array(),
			ANSWYR_VERSION,
			true
		);
		wp_localize_script(
			'answyr-settings',
			'AnswyrSettings',
			array(
				'models'  => Answyr_Provider_Registry::model_choices(),
				'restUrl' => esc_url_raw( rest_url( 'answyr/v1/test-connection' ) ),
				'nonce'   => wp_create_nonce( 'wp_rest' ),
			)
		);
	}

	/** Persist the settings form. */
	public static function handle_save(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'answyr' ) );
		}
		check_admin_referer( 'answyr_settings' );

		self::set( 'provider', sanitize_key( wp_unslash( $_POST['provider'] ?? 'anthropic' ) ) );
		self::set( 'model', sanitize_text_field( wp_unslash( $_POST['model'] ?? '' ) ) );
		self::set( 'max_tokens', max( 256, (int) ( $_POST['max_tokens'] ?? 1500 ) ) );
		self::set( 'temperature', max( 0, min( 1, (float) ( $_POST['temperature'] ?? 0.3 ) ) ) );
		self::set( 'agent_signature', sanitize_textarea_field( wp_unslash( $_POST['agent_signature'] ?? '' ) ) );
		self::set( 'notify_email', sanitize_email( wp_unslash( $_POST['notify_email'] ?? '' ) ) );
		self::set( 'portal_url', esc_url_raw( wp_unslash( $_POST['portal_url'] ?? '' ) ) );

		// Chat widget appearance + copy.
		self::set( 'chat_title', sanitize_text_field( wp_unslash( $_POST['chat_title'] ?? '' ) ) );
		self::set( 'chat_welcome', sanitize_textarea_field( wp_unslash( $_POST['chat_welcome'] ?? '' ) ) );
		self::set( 'chat_placeholder', sanitize_text_field( wp_unslash( $_POST['chat_placeholder'] ?? '' ) ) );
		$chat_color = sanitize_hex_color( wp_unslash( $_POST['chat_color'] ?? '' ) );
		self::set( 'chat_color', $chat_color ? $chat_color : '#2271b1' );
		$display_mode = sanitize_key( wp_unslash( $_POST['chat_display_mode'] ?? 'sitewide' ) );
		self::set( 'chat_display_mode', in_array( $display_mode, array( 'sitewide', 'shortcode' ), true ) ? $display_mode : 'sitewide' );

		// Abuse guards for the unauthenticated chat endpoint (denial-of-wallet).
		self::set( 'chat_require_login', isset( $_POST['chat_require_login'] ) ? 1 : 0 );
		self::set( 'chat_daily_cap', max( 0, (int) ( $_POST['chat_daily_cap'] ?? 500 ) ) );

		// Saved replies: zip the title/body row arrays, dropping blank rows.
		$titles = isset( $_POST['canned_title'] ) ? (array) wp_unslash( $_POST['canned_title'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$bodies = isset( $_POST['canned_body'] ) ? (array) wp_unslash( $_POST['canned_body'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$canned = array();
		foreach ( $titles as $i => $title ) {
			$title = sanitize_text_field( (string) $title );
			$body  = sanitize_textarea_field( (string) ( $bodies[ $i ] ?? '' ) );
			if ( '' !== $title || '' !== $body ) {
				$canned[] = array(
					'title' => '' !== $title ? $title : __( 'Untitled', 'answyr' ),
					'body'  => $body,
				);
			}
		}
		self::set( 'canned', $canned );

		// Intake fields (Desk): zip the label/type/required rows into stable field
		// defs. Guarded on the helper — with Desk off the class isn't loaded and the
		// form doesn't render, so leave any saved fields untouched.
		if ( class_exists( 'Answyr_Intake_Fields' ) && isset( $_POST['intake_label'] ) ) {
			$labels    = (array) wp_unslash( $_POST['intake_label'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$types     = isset( $_POST['intake_type'] ) ? (array) wp_unslash( $_POST['intake_type'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$required  = isset( $_POST['intake_required'] ) ? (array) wp_unslash( $_POST['intake_required'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
			$intake    = array();
			$used_keys = array();
			foreach ( $labels as $i => $label ) {
				$label = sanitize_text_field( (string) $label );
				if ( '' === $label ) {
					continue;
				}
				$type = sanitize_key( (string) ( $types[ $i ] ?? 'text' ) );
				if ( ! in_array( $type, Answyr_Intake_Fields::TYPES, true ) ) {
					$type = 'text';
				}
				// Derive a stable, unique key from the label.
				$base = sanitize_key( str_replace( ' ', '_', $label ) );
				if ( '' === $base ) {
					$base = 'field';
				}
				$key = $base;
				$n   = 2;
				while ( in_array( $key, $used_keys, true ) ) {
					$key = $base . '_' . $n;
					++$n;
				}
				$used_keys[] = $key;
				$intake[]    = array(
					'key'      => $key,
					'label'    => $label,
					'type'     => $type,
					'required' => ! empty( $required[ $i ] ),
				);
			}
			self::set( 'intake_fields', $intake );
		}

		// Macros (Pro) — premium-only class; guarded so the free build no-ops.
		if ( class_exists( 'Answyr_Macros' ) ) {
			Answyr_Macros::save_from_post();
		}

		// Brand voice (Pro) — premium-only feature; only save when its class is present.
		if ( class_exists( 'Answyr_Desk_AI' ) ) {
			self::set( 'brand_voice', sanitize_textarea_field( wp_unslash( $_POST['brand_voice'] ?? '' ) ) );
		}

		self::set( 'module_desk', isset( $_POST['module_desk'] ) ? 1 : 0 );
		self::set( 'module_chat', isset( $_POST['module_chat'] ) ? 1 : 0 );

		// Knowledge-base source selection (moved here from the old Knowledge screen).
		$detected = ( new Answyr_Retrieval() )->detected_post_types();
		$included = isset( $_POST['answyr_sources'] ) ? array_map( 'sanitize_key', (array) wp_unslash( $_POST['answyr_sources'] ) ) : array();
		self::set( 'excluded_post_types', array_values( array_diff( $detected, $included ) ) );

		// Per-provider keys: only overwrite when a new value was typed (placeholder left blank = keep).
		foreach ( array_keys( Answyr_Provider_Registry::all() ) as $pid ) {
			$field = 'api_key_' . $pid;
			if ( ! isset( $_POST[ $field ] ) ) {
				continue;
			}
			$value = trim( (string) wp_unslash( $_POST[ $field ] ) );
			if ( '' !== $value ) {
				self::set_api_key( $pid, $value );
			}
		}

		wp_safe_redirect(
			add_query_arg(
				array(
					'page'    => 'answyr-settings',
					'updated' => '1',
				),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	/** Render the settings page. */
	public static function render_page(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		require ANSWYR_PATH . 'inc/core/views/settings.php';
	}
}
