<?php
/**
 * Conversations Endpoint for BoostBot REST API.
 *
 * @package BoostBot
 */

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

/**
 * BoostBot_Conversations_Endpoint class.
 *
 * Handles retrieval of conversation data (Admin only).
 *
 * @since 1.0.0
 */
class BoostBot_Conversations_Endpoint extends BoostBot_Base_Endpoint
{
    /**
     * The base path for this endpoint.
     *
     * @since 1.0.0
     * @access protected
     * @var string $rest_base
     */
    protected $rest_base = 'conversations';

    /**
     * Register the routes for this endpoint.
     *
     * @since 1.0.0
     */
    public function register()
    {
        // GET /wp-json/boostbot/v1/conversations - List all
        $this->register_route(
            '',
            'GET',
            [$this, 'get_conversations'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/conversations/stats - Statistics
        $this->register_route(
            '/stats',
            'GET',
            [$this, 'get_stats'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/conversations/{id} - Single conversation
        $this->register_route(
            '/(?P<id>\d+)',
            'GET',
            [$this, 'get_conversation'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/conversations/by-id/{conversation_id} - By conversation_id
        $this->register_route(
            '/by-id/(?P<conversation_id>[a-zA-Z0-9-]+)',
            'GET',
            [$this, 'get_by_conversation_id'],
            [$this, 'admin_access']
        );

        // DELETE /wp-json/boostbot/v1/conversations/{id} - Delete
        $this->register_route(
            '/(?P<id>\d+)',
            'DELETE',
            [$this, 'delete_conversation'],
            [$this, 'admin_access']
        );
    }

    /**
     * Get list of conversations.
     *
     * @since 1.0.0
     * @param WP_REST_Request $request The REST request object.
     * @return WP_REST_Response The response.
     */
    public function get_conversations($request)
    {
        BoostBot_Logger::debug('Getting conversations list');

        // Get pagination
        $pagination = $this->get_pagination_params($request);

        // Get sorting
        $allowed_sort_fields = ['id', 'started_at', 'last_message_at', 'total_messages'];
        $sorting = $this->get_sorting_params($request, 'started_at', $allowed_sort_fields);

        // Build filters
        $filters = [];

        // Language filter — scope conversations to selected admin language.
        $language = $this->get_request_language( $request );
        if ( '' !== $language && 'all' !== $language ) {
            $filters['language'] = $language;
        }

        // Filter by conversion status
        if ($request->has_param('converted')) {
            $filters['conversion_achieved'] = (bool) $request->get_param('converted');
        }

        $model = new BoostBot_Conversation_Model();

        $args = [
            'where'    => $filters,
            'order_by' => $sorting['orderby'],
            'order'    => $sorting['order'],
            'limit'    => $pagination['per_page'],
            'offset'   => $pagination['offset'],
        ];

        $conversations = $model->get_all($args);
        $total = $model->count($filters);

        // Enrich with lead info
        $lead_model = new BoostBot_Lead_Model();
        foreach ($conversations as &$conv) {
            $conv->lead = null;
            $context_data = ! empty( $conv->context_data ) ? json_decode( $conv->context_data, true ) : array();
            if ( ! is_array( $context_data ) ) {
                $context_data = array();
            }

            // Normalize title/source fields for admin list rendering.
            $conv->conversation_title = '';
            if ( ! empty( $context_data['conversation_title'] ) ) {
                $conv->conversation_title = sanitize_text_field( (string) $context_data['conversation_title'] );
            } elseif ( ! empty( $conv->page_title ) ) {
                $conv->conversation_title = sanitize_text_field( (string) $conv->page_title );
            } elseif ( ! empty( $context_data['page_title'] ) ) {
                $conv->conversation_title = sanitize_text_field( (string) $context_data['page_title'] );
            }

            $page_title = '';
            if ( ! empty( $conv->page_title ) ) {
                $page_title = sanitize_text_field( (string) $conv->page_title );
            } elseif ( ! empty( $context_data['page_title'] ) ) {
                $page_title = sanitize_text_field( (string) $context_data['page_title'] );
            }

            $page_url = '';
            if ( ! empty( $conv->page_url ) ) {
                $page_url = esc_url_raw( (string) $conv->page_url );
            } elseif ( ! empty( $context_data['page_url'] ) ) {
                $page_url = esc_url_raw( (string) $context_data['page_url'] );
            }

            $bot_name = '';
            if ( ! empty( $context_data['bot_name'] ) ) {
                $bot_name = sanitize_text_field( (string) $context_data['bot_name'] );
            }

            $source_parts = array();
            if ( '' !== $page_title ) {
                $source_parts[] = $page_title;
            }
            if ( '' !== $bot_name ) {
                $source_parts[] = $bot_name;
            }

            $conv->page_title = $page_title;
            $conv->page_url = $page_url;
            $conv->bot_name = $bot_name;
            $conv->source_label = ! empty( $source_parts ) ? implode( ' • ', $source_parts ) : '';

            if (!empty($conv->conversation_id)) {
                $leads = $lead_model->get_all([
                    'where' => ['conversation_id' => $conv->conversation_id],
                    'limit' => 1,
                ]);
                if (!empty($leads)) {
                    $conv->lead = $leads[0];
                }
            }
        }

        return $this->success([
            'conversations' => $conversations,
            'pagination'    => $this->build_pagination_response(
                $pagination['page'],
                $pagination['per_page'],
                $total
            ),
        ]);
    }

    /**
     * Get a single conversation by DB ID.
     *
     * @since 1.0.0
     * @param WP_REST_Request $request The REST request object.
     * @return WP_REST_Response|WP_Error The response or error.
     */
    public function get_conversation($request)
    {
        $id = absint($request->get_param('id'));

        BoostBot_Logger::debug('Getting conversation', ['id' => $id]);

        $model = new BoostBot_Conversation_Model();
        $conversation = $model->get($id);

        if (!$conversation) {
            return $this->not_found('Conversation');
        }

        // Get associated lead
        $lead = null;
        if (!empty($conversation->conversation_id)) {
            $lead_model = new BoostBot_Lead_Model();
            $leads = $lead_model->get_all([
                'where' => ['conversation_id' => $conversation->conversation_id],
                'limit' => 1,
            ]);
            if (!empty($leads)) {
                $lead = $leads[0];
            }
        }

        // Parse JSON fields (with null checks for PHP 8.1+ compatibility)
        $conversation->session_data = !empty($conversation->session_data) ? json_decode($conversation->session_data, true) : [];
        $conversation->context_data = !empty($conversation->context_data) ? json_decode($conversation->context_data, true) : [];
        $conversation->widgets_used = !empty($conversation->widgets_used) ? json_decode($conversation->widgets_used, true) : [];

        // Look up meetings booked during this conversation
        $meetings = array();
        if ( ! empty( $conversation->conversation_id ) ) {
            $meeting_model = new BoostBot_Meeting_Model();
            $all_meetings = $meeting_model->get_all( array(
                'where' => array( 'conversation_id' => $conversation->conversation_id ),
            ) );
            if ( ! empty( $all_meetings ) ) {
                $meetings = $all_meetings;
            }
        }

        return $this->success([
            'conversation' => $conversation,
            'lead'         => $lead,
            'meetings'     => $meetings,
        ]);
    }

    /**
     * Get conversation by conversation_id.
     *
     * @since 1.0.0
     * @param WP_REST_Request $request The REST request object.
     * @return WP_REST_Response|WP_Error The response or error.
     */
    public function get_by_conversation_id($request)
    {
        $conversation_id = sanitize_text_field($request->get_param('conversation_id'));

        BoostBot_Logger::debug('Getting conversation by ID', ['conversation_id' => $conversation_id]);

        $model = new BoostBot_Conversation_Model();
        $conversation = $model->get_by_conversation_id($conversation_id);

        if (!$conversation) {
            return $this->not_found('Conversation');
        }

        // Parse JSON fields (with null checks for PHP 8.1+ compatibility)
        $conversation->session_data = !empty($conversation->session_data) ? json_decode($conversation->session_data, true) : [];
        $conversation->context_data = !empty($conversation->context_data) ? json_decode($conversation->context_data, true) : [];
        $conversation->widgets_used = !empty($conversation->widgets_used) ? json_decode($conversation->widgets_used, true) : [];

        return $this->success([
            'conversation' => $conversation,
        ]);
    }

    /**
     * Delete a conversation.
     *
     * @since 1.0.0
     * @param WP_REST_Request $request The REST request object.
     * @return WP_REST_Response|WP_Error The response or error.
     */
    public function delete_conversation($request)
    {
        $id = absint($request->get_param('id'));

        BoostBot_Logger::debug('Deleting conversation', ['id' => $id]);

        $model = new BoostBot_Conversation_Model();

        if (!$model->get($id)) {
            return $this->not_found('Conversation');
        }

        $result = $model->delete($id);

        if (!$result) {
            return $this->error(__('Failed to delete conversation', 'boostbot'), 500);
        }

        BoostBot_Logger::info('Conversation deleted', ['id' => $id]);

        return $this->success([
            'message' => __('Conversation deleted successfully', 'boostbot'),
        ]);
    }

    /**
     * Get conversation statistics.
     *
     * @since 1.0.0
     * @param WP_REST_Request $request The REST request object.
     * @return WP_REST_Response The response.
     */
    public function get_stats($request)
    {
        BoostBot_Logger::debug('Getting conversation stats');

        global $wpdb;
        $table = self::sanitize_identifier( $wpdb->prefix . 'boostbot_conversations' );

        // Total conversations
        $total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().

        // Today's conversations
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $today = (int) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT COUNT(*) FROM {$table} WHERE DATE(started_at) = CURDATE()"
        );

        // This week
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $this_week = (int) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT COUNT(*) FROM {$table} WHERE YEARWEEK(started_at, 1) = YEARWEEK(CURDATE(), 1)"
        );

        // This month
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $this_month = (int) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT COUNT(*) FROM {$table} WHERE MONTH(started_at) = MONTH(CURDATE()) AND YEAR(started_at) = YEAR(CURDATE())"
        );

        // Conversion rate
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $converted = (int) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT COUNT(*) FROM {$table} WHERE conversion_achieved = 1"
        );
        $conversion_rate = $total > 0 ? round(($converted / $total) * 100, 1) : 0;

        // Average messages per conversation
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $avg_messages = (float) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT AVG(total_messages) FROM {$table}"
        );

        // Average satisfaction score
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $avg_satisfaction = (float) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT AVG(satisfaction_score) FROM {$table} WHERE satisfaction_score IS NOT NULL"
        );

        // Average duration (seconds)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $avg_duration = (float) $wpdb->get_var(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            "SELECT AVG(duration_seconds) FROM {$table} WHERE duration_seconds > 0"
        );

        $stats = [
            'total'            => $total,
            'today'            => $today,
            'this_week'        => $this_week,
            'this_month'       => $this_month,
            'converted'        => $converted,
            'conversion_rate'  => $conversion_rate,
            'avg_messages'     => round($avg_messages, 1),
            'avg_satisfaction' => round($avg_satisfaction, 1),
            'avg_duration'     => round($avg_duration),
        ];

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

    /**
     * Restrict a SQL identifier to [A-Za-z0-9_] characters.
     *
     * DDL cannot use $wpdb->prepare() for identifiers, 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 );
    }
}






