<?php
/**
 * Backup (Export/Import) Endpoint for BoostBot REST API.
 *
 * Provides two export types:
 *   1. Configuration export  - settings, widgets, bots, personalities, tones, presets, etc.
 *   2. Data export           - leads, conversations, meetings, contacts, analytics, usage.
 *
 * And matching import for both types.
 *
 * @package BoostBot
 * @since   1.0.3
 */

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

/**
 * BoostBot_Backup_Endpoint class.
 *
 * @since 1.0.3
 */
class BoostBot_Backup_Endpoint extends BoostBot_Base_Endpoint {

    /**
     * The base path for this endpoint.
     *
     * @var string
     */
    protected $rest_base = 'backup';

    /**
     * Register the routes for this endpoint.
     */
    public function register() {
        // Export configuration (settings, bots, widgets, etc.)
        $this->register_route(
            '/export/config',
            'GET',
            [ $this, 'export_config' ],
            [ $this, 'admin_access' ]
        );

        // Export data (leads, conversations, meetings, etc.)
        $this->register_route(
            '/export/data',
            'GET',
            [ $this, 'export_data' ],
            [ $this, 'admin_access' ]
        );

        // Import configuration
        $this->register_route(
            '/import/config',
            'POST',
            [ $this, 'import_config' ],
            [ $this, 'admin_access' ]
        );

        // Import data
        $this->register_route(
            '/import/data',
            'POST',
            [ $this, 'import_data' ],
            [ $this, 'admin_access' ]
        );
    }

    // =====================================================================
    // EXPORT: Configuration
    // =====================================================================

    /**
     * Export all plugin configuration as JSON.
     *
     * Includes: settings (options), widgets, bots, personalities, tones,
     * appearance presets, opening screen presets, form fields,
     * contact lists, galleries, gallery images.
     *
     * Does NOT include: leads, conversations, meetings, analytics, usage,
     * knowledge index, cache, or any visitor/customer data.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function export_config( $request ) {
        global $wpdb;

        $export = [
            'export_type'    => 'boostbot_config',
            'export_version' => '1.0',
            'plugin_version' => defined( 'BOOSTBOT_VERSION' ) ? BOOSTBOT_VERSION : '1.0.0',
            'site_url'       => get_site_url(),
            'exported_at'    => current_time( 'c' ),
        ];

        // 1. Plugin options (settings)
        $export['settings'] = $this->get_all_boostbot_options();

        // 2. Configuration tables
        $config_tables = [
            'widgets'                => 'boostbot_widgets',
            'chatbot_widgets'        => 'boostbot_chatbot_widgets',
            'form_fields'            => 'boostbot_form_fields',
            'bots'                   => 'boostbot_bots',
            'personalities'          => 'boostbot_personalities',
            'personality_traits'     => 'boostbot_personality_traits',
            'tones'                  => 'boostbot_tones',
            'appearance_presets'     => 'boostbot_appearance_presets',
            'opening_screen_presets' => 'boostbot_opening_screen_presets',
            'contact_lists'          => 'boostbot_contact_lists',
            'galleries'              => 'boostbot_galleries',
            'gallery_images'         => 'boostbot_gallery_images',
            'bundles'                => 'boostbot_bundles',
            'site_profile'           => 'boostbot_site_profile',
        ];

        foreach ( $config_tables as $key => $table_suffix ) {
            $table_name = $wpdb->prefix . $table_suffix;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $rows = $wpdb->get_results( "SELECT * FROM `{$table_name}`", ARRAY_A );
            $export[ $key ] = is_array( $rows ) ? $rows : [];
        }

        return $this->success( $export );
    }

    // =====================================================================
    // EXPORT: Data
    // =====================================================================

    /**
     * Export all cumulative data as JSON.
     *
     * Includes: leads, conversations, meetings, contacts, analytics,
     * widget usage.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function export_data( $request ) {
        global $wpdb;

        $export = [
            'export_type'    => 'boostbot_data',
            'export_version' => '1.0',
            'plugin_version' => defined( 'BOOSTBOT_VERSION' ) ? BOOSTBOT_VERSION : '1.0.0',
            'site_url'       => get_site_url(),
            'exported_at'    => current_time( 'c' ),
        ];

        $data_tables = [
            'leads'         => 'boostbot_leads',
            'conversations' => 'boostbot_conversations',
            'meetings'      => 'boostbot_meetings',
            'contacts'      => 'boostbot_contacts',
            'analytics'     => 'boostbot_analytics',
            'widget_usage'  => 'boostbot_widget_usage',
        ];

        foreach ( $data_tables as $key => $table_suffix ) {
            $table_name = $wpdb->prefix . $table_suffix;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $rows = $wpdb->get_results( "SELECT * FROM `{$table_name}`", ARRAY_A );
            $export[ $key ] = is_array( $rows ) ? $rows : [];
        }

        return $this->success( $export );
    }

    // =====================================================================
    // IMPORT: Configuration
    // =====================================================================

    /**
     * Import plugin configuration from JSON.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function import_config( $request ) {
        $body = $request->get_json_params();

        if ( empty( $body ) || ! isset( $body['export_type'] ) ) {
            return $this->error( __( 'Invalid import file format.', 'boostbot' ), 400 );
        }

        if ( 'boostbot_config' !== $body['export_type'] ) {
            return $this->error( __( 'This file contains data, not configuration. Use the data import instead.', 'boostbot' ), 400 );
        }

        $import_mode = sanitize_text_field( $request->get_param( 'import_mode' ) ?: ( $body['import_mode'] ?? 'skip' ) );
        if ( ! in_array( $import_mode, [ 'skip', 'replace' ], true ) ) {
            $import_mode = 'skip';
        }

        $results = [ 'imported' => [], 'errors' => [], 'import_mode' => $import_mode ];

        // 1. Import settings (options) — always uses update_option (upsert)
        if ( ! empty( $body['settings'] ) && is_array( $body['settings'] ) ) {
            $count = $this->import_options( $body['settings'] );
            $results['imported']['settings'] = $count;
        }

        // 2. Import config tables
        $config_tables = [
            'widgets', 'chatbot_widgets', 'form_fields',
            'bots', 'personalities', 'personality_traits', 'tones',
            'appearance_presets', 'opening_screen_presets',
            'contact_lists', 'galleries', 'gallery_images',
            'bundles', 'site_profile',
        ];

        foreach ( $config_tables as $key ) {
            if ( ! empty( $body[ $key ] ) && is_array( $body[ $key ] ) ) {
                $table_suffix = 'boostbot_' . $key;
                $count = $this->import_table_rows( $table_suffix, $body[ $key ], $import_mode );
                if ( is_wp_error( $count ) ) {
                    $results['errors'][] = $key . ': ' . $count->get_error_message();
                } else {
                    $results['imported'][ $key ] = $count;
                }
            }
        }

        return $this->success( $results );
    }

    // =====================================================================
    // IMPORT: Data
    // =====================================================================

    /**
     * Import cumulative data from JSON.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function import_data( $request ) {
        $body = $request->get_json_params();

        if ( empty( $body ) || ! isset( $body['export_type'] ) ) {
            return $this->error( __( 'Invalid import file format.', 'boostbot' ), 400 );
        }

        if ( 'boostbot_data' !== $body['export_type'] ) {
            return $this->error( __( 'This file contains configuration, not data. Use the config import instead.', 'boostbot' ), 400 );
        }

        $import_mode = sanitize_text_field( $request->get_param( 'import_mode' ) ?: ( $body['import_mode'] ?? 'skip' ) );
        if ( ! in_array( $import_mode, [ 'skip', 'replace' ], true ) ) {
            $import_mode = 'skip';
        }

        $results = [ 'imported' => [], 'errors' => [], 'import_mode' => $import_mode ];

        $data_tables = [
            'leads', 'conversations', 'meetings',
            'contacts', 'analytics', 'widget_usage',
        ];

        foreach ( $data_tables as $key ) {
            if ( ! empty( $body[ $key ] ) && is_array( $body[ $key ] ) ) {
                $table_suffix = 'boostbot_' . $key;
                $count = $this->import_table_rows( $table_suffix, $body[ $key ], $import_mode );
                if ( is_wp_error( $count ) ) {
                    $results['errors'][] = $key . ': ' . $count->get_error_message();
                } else {
                    $results['imported'][ $key ] = $count;
                }
            }
        }

        return $this->success( $results );
    }

    // =====================================================================
    // HELPERS
    // =====================================================================

    /**
     * Get all boostbot_ options except sensitive keys.
     *
     * @return array Associative array of option_name => option_value.
     */
    private function get_all_boostbot_options() {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $rows = $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s",
                $wpdb->esc_like( 'boostbot_' ) . '%'
            ),
            ARRAY_A
        );

        $options = [];
        // Sensitive keys to exclude from export
        $sensitive_keys = [
            'boostbot_backend_site_api_key',
            'boostbot_api_key',
            'boostbot_openai_api_key',
            'boostbot_crm_webhook_api_key',
            'boostbot_crm_api_key',
            'boostbot_license_key',
            'boostbot_management_key_plaintext',
            'boostbot_management_key_hash',
            'boostbot_gemini_api_key',
            'boostbot_voyage_api_key',
            'boostbot_google_access_token',
            'boostbot_google_refresh_token',
        ];

        if ( is_array( $rows ) ) {
            foreach ( $rows as $row ) {
                // Skip sensitive keys
                if ( in_array( $row['option_name'], $sensitive_keys, true ) ) {
                    continue;
                }
                // Skip transient-like and migration flags
                if ( strpos( $row['option_name'], '_transient_' ) === 0 ) {
                    continue;
                }
                $options[ $row['option_name'] ] = maybe_unserialize( $row['option_value'] );
            }
        }

        return $options;
    }

    /**
     * Import options from an associative array.
     *
     * @param array $options Associative array of option_name => option_value.
     * @return int Number of options imported.
     */
    private function import_options( $options ) {
        $count = 0;

        // Sensitive keys to never import
        $sensitive_keys = [
            'boostbot_backend_site_api_key',
            'boostbot_api_key',
            'boostbot_openai_api_key',
            'boostbot_crm_webhook_api_key',
            'boostbot_crm_api_key',
            'boostbot_license_key',
            'boostbot_management_key_plaintext',
            'boostbot_management_key_hash',
            'boostbot_gemini_api_key',
            'boostbot_voyage_api_key',
            'boostbot_google_access_token',
            'boostbot_google_refresh_token',
        ];

        foreach ( $options as $name => $value ) {
            // Safety: only import boostbot_ prefixed options
            if ( strpos( $name, 'boostbot_' ) !== 0 ) {
                continue;
            }
            // Skip sensitive keys
            if ( in_array( $name, $sensitive_keys, true ) ) {
                continue;
            }
            update_option( $name, $value );
            $count++;
        }

        return $count;
    }

    /**
     * Import rows into a table.
     *
     * @param string $table_suffix Table name without prefix (e.g. 'boostbot_leads').
     * @param array  $rows         Array of row associative arrays.
     * @param string $mode         Import mode: 'skip' (INSERT IGNORE) or 'replace' (REPLACE INTO).
     * @return int|\WP_Error Number of rows imported or error.
     */
    private function import_table_rows( $table_suffix, $rows, $mode = 'skip' ) {
        global $wpdb;

        // Restrict the table name to safe identifier characters before it is
        // ever interpolated into SQL. Callers currently pass the suffix from a
        // hard-coded allow-list, but the input passes through JSON-decoded
        // request bodies in parts of the import flow — never trust the source.
        $table_name = self::sanitize_identifier( $wpdb->prefix . $table_suffix );

        // Check table exists (use prepare() for the LIKE value).
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $exists = $wpdb->get_var(
            $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name )
        );
        if ( ! $exists ) {
            return new \WP_Error( 'table_missing', sprintf( 'Table %s does not exist.', $table_name ) );
        }

        $sql_verb = 'replace' === $mode ? 'REPLACE INTO' : 'INSERT IGNORE INTO';

        $count = 0;
        foreach ( $rows as $row ) {
            if ( ! is_array( $row ) ) {
                continue;
            }

            // Column names come from the JSON request body — sanitize every
            // identifier before interpolation. Drop any columns that don't
            // survive the allow-list rather than execute SQL with mangled
            // names.
            $safe_columns = array();
            $values       = array();
            foreach ( $row as $col_name => $col_value ) {
                $safe_col = self::sanitize_identifier( (string) $col_name );
                if ( '' === $safe_col ) {
                    continue;
                }
                $safe_columns[] = $safe_col;
                $values[]       = $col_value;
            }
            if ( empty( $safe_columns ) ) {
                continue;
            }

            $column_list      = implode( ', ', array_map( function( $col ) { return "`{$col}`"; }, $safe_columns ) );
            $placeholder_list = implode( ', ', array_fill( 0, count( $safe_columns ), '%s' ) );

            // $sql_verb is a hard-coded literal; $table_name and every column
            // identifier have been passed through sanitize_identifier; row
            // values bind through prepare() placeholders.
            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $placeholder_list is a dynamic %s,... list from array_fill; $table_name sanitized via sanitize_identifier; values bind via prepare().
            $result = $wpdb->query(
                $wpdb->prepare(
                    "{$sql_verb} `{$table_name}` ({$column_list}) VALUES ({$placeholder_list})",
                    $values
                )
            );
            // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter

            if ( false !== $result ) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * Restrict a SQL identifier to [A-Za-z0-9_] characters.
     *
     * $wpdb->prepare() does not accept placeholders for table or column
     * identifiers on all supported WP versions, so any identifier that ends
     * up interpolated into a query first passes through this allow-list.
     *
     * @param string $identifier Raw identifier.
     * @return string Identifier with disallowed characters removed.
     */
    private static function sanitize_identifier( $identifier ) {
        return preg_replace( '/[^A-Za-z0-9_]/', '', (string) $identifier );
    }
}
