<?php
/**
 * Bot Model for database operations.
 *
 * @package BoostBot
 * @since   1.1.0
 */

// Prevent direct access
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class BoostBot_Bot_Model
 *
 * Handles CRUD operations for the bots table.
 *
 * @since 1.1.0
 */
class BoostBot_Bot_Model extends BoostBot_Base_Model {

    use BoostBot_Localizable;

    /**
     * Constructor.
     */
    public function __construct() {
        parent::__construct();

        $this->table_name = self::sanitize_identifier( $this->wpdb->prefix . 'boostbot_bots' );

        $this->fillable = array(
            // Basic info
            'bot_name',
            'bot_slug',
            'language',
            'description',
            'is_active',
            'test_mode',
            'is_default',
            'welcome_message',
            'show_welcome_message',
            'position',

            // Week 28: per-bot output cap (chars). Null means "use global".
            'bot_output_char_limit',

            // Week 27.6: BrandCard override (per-bot)
            'brand_card_mode',
            'brand_card_override',
            
            // Appearance preset
            'appearance_preset_id',
            
            // Opening screen preset
            'opening_screen_preset_id',
            'opening_screen_overrides',

            // WooCommerce preset
            'woocommerce_preset_id',
            
            // Colors
            'primary_color',
            'secondary_color',
            'background_color',
            'header_background',
            'header_text_color',
            'user_message_bg',
            'user_message_text',
            'bot_message_bg',
            'bot_message_text',
            'input_bg',
            'input_text',
            'input_border',
            'button_bg',
            'button_text',
            'button_hover_bg',
            'link_color',
            
            // Typography
            'font_family',
            'font_size_base',
            'font_size_header',
            'font_weight_normal',
            'font_weight_bold',
            'line_height',
            'letter_spacing',
            
            // Bubble
            'bubble_icon',
            'bubble_size',
            'bubble_custom_icon',
            'bubble_animation',
            
            // Window
            'window_width',
            'window_height',
            'window_border_radius',
            'window_shadow',
            
            // Custom CSS
            'custom_css',
            
            // Appearance overrides (JSON)
            'appearance_overrides',
            
            // Behavior / Personality
            'personality_id',
            'system_prompt',
            'tone',
            'proactive_engagement',
            'proactive_delay_seconds',
            'personality_overrides',
            'personality_traits',
            'custom_instructions',
            'rules',
            'forbidden_topics',
            'display_conditions',
            'priority',
            'knowledge_files',
            'attachment_files',
            'enabled_coupons',
            'contact_list_id',
            'enabled_widgets',
            'created_by',
        );

        $this->column_types = array(
            'id'                       => 'bigint',
            'is_active'                => 'tinyint',
            'test_mode'                => 'tinyint',
            'show_welcome_message'     => 'tinyint',
            'is_default'               => 'tinyint',
            'priority'                 => 'int',
            'personality_id'           => 'bigint',
            'contact_list_id'          => 'bigint',
            'appearance_preset_id'     => 'bigint',
            'opening_screen_preset_id' => 'bigint',
            'woocommerce_preset_id'    => 'bigint',
            'bot_output_char_limit'    => 'int',
            'proactive_delay_seconds'  => 'int',
            'created_by'               => 'bigint',
        );
    }

    /**
     * Create a new bot.
     * If is_default is set to 1, removes default from other bots in the same language.
     *
     * @param array $data Bot data.
     * @return int|false The new bot ID or false on failure.
     */
    public function create( $data ) {
        $language = $data['language'] ?? BoostBot_Language::get_primary();

        // Validate WooCommerce preset language matches bot language.
        if ( ! empty( $data['woocommerce_preset_id'] ) && class_exists( 'BoostBot_WooCommerce_Preset_Model' ) ) {
            $preset_model = new BoostBot_WooCommerce_Preset_Model();
            $preset       = $preset_model->get( $data['woocommerce_preset_id'] );
            if ( $preset && isset( $preset->language ) && $preset->language !== $language ) {
                return new \WP_Error(
                    'language_mismatch',
                    __( 'WooCommerce preset language must match bot language.', 'boostbot' ),
                    array( 'status' => 400 )
                );
            }
        }

        $result = parent::create( $data );

        // Only clear the previous default AFTER the new bot is successfully
        // created, so a validation/insert failure never leaves the language
        // with no default at all.
        if ( $result && ! is_wp_error( $result ) && ! empty( $data['is_default'] ) ) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $this->wpdb->update(
                $this->table_name,
                array( 'is_default' => 0 ),
                array( 'is_default' => 1, 'language' => $language ),
                array( '%d' ),
                array( '%d', '%s' )
            );
            // The bulk clear above also cleared the just-created bot; re-assert it
            // as the sole default for its language.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $this->wpdb->update(
                $this->table_name,
                array( 'is_default' => 1 ),
                array( 'id' => (int) $result ),
                array( '%d' ),
                array( '%d' )
            );
        }

        return $result;
    }

    /**
     * Update a bot.
     * If is_default is set to 1, removes default from other bots in the same language.
     *
     * @param int   $id   Bot ID.
     * @param array $data Bot data.
     * @return bool True on success.
     */
    public function update( $id, $data ) {
        // Validate WooCommerce preset language matches bot language.
        if ( class_exists( 'BoostBot_WooCommerce_Preset_Model' ) ) {
            $existing     = $this->get( $id );
            $bot_language = $data['language'] ?? ( $existing->language ?? BoostBot_Language::get_primary() );
            $preset_id    = ! empty( $data['woocommerce_preset_id'] )
                ? (int) $data['woocommerce_preset_id']
                : (int) ( $existing->woocommerce_preset_id ?? 0 );

            if ( $preset_id > 0 ) {
                $preset_model = new BoostBot_WooCommerce_Preset_Model();
                $preset       = $preset_model->get( $preset_id );
                if ( $preset && isset( $preset->language ) && $preset->language !== $bot_language ) {
                    return new \WP_Error(
                        'language_mismatch',
                        __( 'WooCommerce preset language must match bot language.', 'boostbot' ),
                        array( 'status' => 400 )
                    );
                }
            }
        }

        $result = parent::update( $id, $data );

        // Only clear the previous default AFTER the update succeeds, so a
        // validation failure never leaves the language with no default.
        if ( $result && ! is_wp_error( $result ) && isset( $data['is_default'] ) && $data['is_default'] ) {
            $language = $data['language'] ?? null;
            if ( ! $language ) {
                $existing = $this->get( $id );
                $language = $existing->language ?? BoostBot_Language::get_primary();
            }
            // Clear the default flag from every other bot in this language...
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $this->wpdb->update(
                $this->table_name,
                array( 'is_default' => 0 ),
                array( 'is_default' => 1, 'language' => $language ),
                array( '%d' ),
                array( '%d', '%s' )
            );
            // ...then re-assert this bot as the sole default (the bulk clear
            // above also cleared it).
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $this->wpdb->update(
                $this->table_name,
                array( 'is_default' => 1 ),
                array( 'id' => (int) $id ),
                array( '%d' ),
                array( '%d' )
            );
        }

        return $result;
    }

    /**
     * Get the default bot, optionally filtered by language.
     *
     * @param string $language Language code (default: any).
     * @return object|null The default bot or null.
     */
    public function get_default( string $language = '' ) {
        $language = class_exists( 'BoostBot_Language' ) ? BoostBot_Language::sanitize( $language ) : $language;
        if ( '' !== $language ) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            return $this->wpdb->get_row(
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                $this->wpdb->prepare(
                    "SELECT * FROM {$this->table_name} WHERE is_default = 1 AND is_active = 1 AND language = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                    $language
                )
            );
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $this->wpdb->get_row(
            "SELECT * FROM {$this->table_name} WHERE is_default = 1 AND is_active = 1 LIMIT 1" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        );
    }

    /**
     * Get bot by slug.
     *
     * @param string $slug The bot slug.
     * @return object|null The bot or null.
     */
    public function get_by_slug( $slug ) {
        $slug = sanitize_title( $slug );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $this->wpdb->get_row(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                "SELECT * FROM {$this->table_name} WHERE bot_slug = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $slug
            )
        );
    }

    /**
     * Get all active bots, optionally filtered by language.
     *
     * @param string $language Language code (empty = all languages).
     * @return array Array of active bots.
     */
    public function get_active( string $language = '' ) {
        $language = class_exists( 'BoostBot_Language' ) ? BoostBot_Language::sanitize( $language ) : $language;
        if ( '' !== $language ) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            return $this->wpdb->get_results(
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                $this->wpdb->prepare(
                    "SELECT * FROM {$this->table_name} WHERE is_active = 1 AND language = %s ORDER BY priority DESC, bot_name ASC", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                    $language
                )
            );
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $this->wpdb->get_results(
            "SELECT * FROM {$this->table_name} WHERE is_active = 1 ORDER BY priority DESC, bot_name ASC" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        );
    }

    /**
     * Set a bot as default within its language.
     *
     * @param int $bot_id The bot ID to set as default.
     * @return bool True on success.
     */
    public function set_default( $bot_id ) {
        $bot = $this->get( $bot_id );
        $language = $bot->language ?? BoostBot_Language::get_primary();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $this->wpdb->update(
            $this->table_name,
            array( 'is_default' => 0 ),
            array( 'is_default' => 1, 'language' => $language ),
            array( '%d' ),
            array( '%d', '%s' )
        );

        return $this->update( $bot_id, array( 'is_default' => 1 ) );
    }

    /**
     * Duplicate a bot.
     *
     * @param int $bot_id The bot ID to duplicate.
     * @return int|false The new bot ID or false on failure.
     */
    public function duplicate( $bot_id ) {
        $bot = $this->get( $bot_id );

        if ( ! $bot ) {
            return false;
        }

        $new_data = (array) $bot;
        unset( $new_data['id'] );

        // Generate new slug and name
        $new_data['bot_name']   .= __( ' (העתק)', 'boostbot' );
        $new_data['bot_slug']    = $this->generate_unique_slug( $bot->bot_slug );
        $new_data['is_default']  = 0;
        $new_data['created_at']  = current_time( 'mysql' );
        $new_data['created_by']  = get_current_user_id();

        return $this->create( $new_data );
    }

    /**
     * Generate a unique slug.
     *
     * @param string $base_slug The base slug.
     * @return string A unique slug.
     */
    private function generate_unique_slug( $base_slug ) {
        $slug    = $base_slug;
        $counter = 1;

        while ( $this->get_by_slug( $slug ) ) {
            $slug = $base_slug . '-' . $counter;
            $counter++;
        }

        return $slug;
    }

    /**
     * Get bots count by status.
     *
     * @return array Array with counts.
     */
    public function get_counts() {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $results = $this->wpdb->get_row(
            "SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active,
                SUM(CASE WHEN is_active = 0 THEN 1 ELSE 0 END) as inactive
             FROM {$this->table_name}" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        );

        return array(
            'total'    => (int) $results->total,
            'active'   => (int) $results->active,
            'inactive' => (int) $results->inactive,
        );
    }

    /**
     * Validate bot data.
     *
     * @param array    $data The data to validate.
     * @param int|null $id   The bot ID for updates.
     * @return true|WP_Error True if valid, WP_Error otherwise.
     */
    public function validate( $data, $id = null ) {
        $errors = new WP_Error();

        // Required: bot_name (only for new bots, not updates)
        if ( null === $id && empty( $data['bot_name'] ) ) {
            $errors->add( 'missing_name', __( 'Bot name is required', 'boostbot' ) );
        }

        // Required for new: bot_slug
        if ( null === $id && empty( $data['bot_slug'] ) ) {
            $errors->add( 'missing_slug', __( 'Bot slug is required', 'boostbot' ) );
        }

        // Unique slug (within same language)
        if ( ! empty( $data['bot_slug'] ) ) {
            $lang     = $data['language'] ?? '';
            $existing = $this->get_by_slug( $data['bot_slug'] );
            if ( $existing && ( null === $id || (int) $existing->id !== (int) $id ) ) {
                if ( '' === $lang || ( isset( $existing->language ) && $existing->language === $lang ) ) {
                    $errors->add( 'duplicate_slug', __( 'This slug is already in use', 'boostbot' ) );
                }
            }
        }

        // Valid position
        $valid_positions = array( 'bottom-right', 'bottom-left', 'top-right', 'top-left' );
        if ( ! empty( $data['position'] ) && ! in_array( $data['position'], $valid_positions, true ) ) {
            $errors->add( 'invalid_position', __( 'Invalid position value', 'boostbot' ) );
        }

        // Valid color
        if ( ! empty( $data['primary_color'] ) && ! preg_match( '/^#[a-fA-F0-9]{6}$/', $data['primary_color'] ) ) {
            $errors->add( 'invalid_color', __( 'Invalid color format', 'boostbot' ) );
        }

        if ( $errors->has_errors() ) {
            return $errors;
        }

        return true;
    }

    /**
     * Sanitize bot data.
     *
     * @param array $data The data to sanitize.
     * @return array The sanitized data.
     */
    public function sanitize( $data ) {
        $sanitized = array();

        if ( isset( $data['bot_name'] ) ) {
            $sanitized['bot_name'] = sanitize_text_field( $data['bot_name'] );
        }

        if ( isset( $data['bot_slug'] ) ) {
            // Force ASCII-only slug (remove Hebrew and special chars)
            $slug = sanitize_title( $data['bot_slug'] );
            // Remove any non-ASCII characters (including encoded Hebrew)
            $slug = preg_replace( '/[^a-z0-9-]/', '', $slug );
            // If empty after sanitization, generate a unique slug
            if ( empty( $slug ) ) {
                $slug = 'bot-' . time();
            }
            $sanitized['bot_slug'] = $slug;
        }

        if ( isset( $data['description'] ) ) {
            $sanitized['description'] = sanitize_textarea_field( $data['description'] );
        }

        if ( isset( $data['is_active'] ) ) {
            $sanitized['is_active'] = absint( $data['is_active'] ) ? 1 : 0;
        }

        if ( isset( $data['test_mode'] ) ) {
            $sanitized['test_mode'] = absint( $data['test_mode'] ) ? 1 : 0;
        }

        if ( isset( $data['is_default'] ) ) {
            $sanitized['is_default'] = absint( $data['is_default'] ) ? 1 : 0;
        }

        if ( isset( $data['welcome_message'] ) ) {
            $sanitized['welcome_message'] = sanitize_textarea_field( $data['welcome_message'] );
        }

        if ( isset( $data['show_welcome_message'] ) ) {
            $sanitized['show_welcome_message'] = absint( $data['show_welcome_message'] ) ? 1 : 0;
        }

        if ( isset( $data['position'] ) ) {
            $sanitized['position'] = sanitize_text_field( $data['position'] );
        }

        // Week 28: per-bot output cap (chars), clamped to global cap.
        if ( array_key_exists( 'bot_output_char_limit', $data ) ) {
            $global_cap = absint( BoostBot_Settings::get( 'bot_output_char_limit_global', 800 ) );
            $raw        = $data['bot_output_char_limit'];
            $val        = is_null( $raw ) || '' === $raw ? 0 : absint( $raw );
            if ( $val <= 0 ) {
                $sanitized['bot_output_char_limit'] = null;
            } else {
                if ( $global_cap > 0 ) {
                    $val = min( $val, $global_cap );
                }
                $sanitized['bot_output_char_limit'] = $val;
            }
        }

        // Week 27.6: BrandCard override mode (global|override)
        if ( array_key_exists( 'brand_card_mode', $data ) ) {
            $mode = sanitize_text_field( (string) $data['brand_card_mode'] );
            $mode = strtolower( $mode );
            if ( 'override' !== $mode ) {
                $mode = 'global';
            }
            $sanitized['brand_card_mode'] = $mode;
        }

        if ( array_key_exists( 'personality_id', $data ) ) {
            $sanitized['personality_id'] = ! empty( $data['personality_id'] ) ? absint( $data['personality_id'] ) : null;
        }

        if ( array_key_exists( 'appearance_preset_id', $data ) ) {
            $sanitized['appearance_preset_id'] = ! empty( $data['appearance_preset_id'] ) ? absint( $data['appearance_preset_id'] ) : null;
        }

        if ( array_key_exists( 'opening_screen_preset_id', $data ) ) {
            $sanitized['opening_screen_preset_id'] = ! empty( $data['opening_screen_preset_id'] ) ? absint( $data['opening_screen_preset_id'] ) : null;
        }

        if ( array_key_exists( 'woocommerce_preset_id', $data ) ) {
            $sanitized['woocommerce_preset_id'] = ! empty( $data['woocommerce_preset_id'] ) ? absint( $data['woocommerce_preset_id'] ) : null;
        }

        // Personality/Behavior fields
        if ( isset( $data['system_prompt'] ) ) {
            $sanitized['system_prompt'] = sanitize_textarea_field( $data['system_prompt'] );
        }

        if ( isset( $data['tone'] ) ) {
            $sanitized['tone'] = sanitize_text_field( $data['tone'] );
        }

        if ( isset( $data['proactive_engagement'] ) ) {
            $sanitized['proactive_engagement'] = $data['proactive_engagement'] ? 1 : 0;
        }

        if ( isset( $data['proactive_delay_seconds'] ) ) {
            // Clamp between 1 and 60 seconds, default 10
            $delay = absint( $data['proactive_delay_seconds'] );
            $sanitized['proactive_delay_seconds'] = max( 1, min( 60, $delay ) );
        }

        // Color fields
        $color_fields = array(
            'primary_color',
            'secondary_color',
            'background_color',
            'header_background',
            'header_text_color',
            'user_message_bg',
            'user_message_text',
            'bot_message_bg',
            'bot_message_text',
            'input_bg',
            'input_text',
            'input_border',
            'button_bg',
            'button_text',
            'button_hover_bg',
            'link_color',
        );

        foreach ( $color_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = sanitize_hex_color( $data[ $field ] ) ?: null;
            }
        }

        // Typography fields
        $typography_fields = array(
            'font_family',
            'font_size_base',
            'font_size_header',
            'font_weight_normal',
            'font_weight_bold',
            'line_height',
            'letter_spacing',
        );

        foreach ( $typography_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = sanitize_text_field( $data[ $field ] ) ?: null;
            }
        }

        // Bubble fields
        if ( isset( $data['bubble_icon'] ) ) {
            $sanitized['bubble_icon'] = sanitize_text_field( $data['bubble_icon'] );
        }

        if ( isset( $data['bubble_size'] ) ) {
            $sanitized['bubble_size'] = sanitize_text_field( $data['bubble_size'] ) ?: null;
        }

        if ( isset( $data['bubble_custom_icon'] ) ) {
            $sanitized['bubble_custom_icon'] = esc_url_raw( $data['bubble_custom_icon'] ) ?: null;
        }

        if ( isset( $data['bubble_animation'] ) ) {
            $sanitized['bubble_animation'] = sanitize_text_field( $data['bubble_animation'] ) ?: null;
        }

        // Window fields
        $window_fields = array( 'window_width', 'window_height', 'window_border_radius' );
        foreach ( $window_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = sanitize_text_field( $data[ $field ] ) ?: null;
            }
        }

        if ( isset( $data['window_shadow'] ) ) {
            // Allow CSS shadow value
            $sanitized['window_shadow'] = wp_kses( $data['window_shadow'], array() ) ?: null;
        }

        // Custom CSS - allow safe CSS
        if ( isset( $data['custom_css'] ) ) {
            $sanitized['custom_css'] = wp_strip_all_tags( $data['custom_css'] ) ?: null;
        }

        // JSON fields
        $json_fields = array(
            'personality_traits',
            'display_conditions',
            'knowledge_files',
            'attachment_files',
            'enabled_coupons',
            'enabled_widgets',
            'appearance_overrides',
            'personality_overrides',
            'opening_screen_overrides',
            // Week 27.6: BrandCard override object
            'brand_card_override',
        );

        foreach ( $json_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = is_string( $data[ $field ] )
                    ? $data[ $field ]
                    : wp_json_encode( $data[ $field ] );
            }
        }

        // Text fields
        $text_fields = array( 'custom_instructions', 'rules', 'forbidden_topics' );
        foreach ( $text_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = sanitize_textarea_field( $data[ $field ] );
            }
        }

        if ( isset( $data['priority'] ) ) {
            $sanitized['priority'] = absint( $data['priority'] );
        }

        if ( array_key_exists( 'contact_list_id', $data ) ) {
            $sanitized['contact_list_id'] = ! empty( $data['contact_list_id'] ) ? absint( $data['contact_list_id'] ) : null;
        }

        if ( isset( $data['created_by'] ) ) {
            $sanitized['created_by'] = absint( $data['created_by'] );
        }

        if ( isset( $data['language'] ) ) {
            $sanitized['language'] = sanitize_text_field( $data['language'] );
        }

        return $sanitized;
    }
}

