<?php
/**
 * Conversation Model for database operations.
 *
 * @package BoostBot
 * @since   1.0.0
 */

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

/**
 * Class BoostBot_Conversation_Model
 *
 * Handles CRUD operations for the conversations table.
 *
 * @since 1.0.0
 */
class BoostBot_Conversation_Model extends BoostBot_Base_Model {

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

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

        $this->fillable = array(
            'language',
            'conversation_id',
            'visitor_id',
            'session_data',
            'context_data',
            'total_messages',
            'bot_messages',
            'user_messages',
            'widgets_used',
            'conversion_achieved',
            'satisfaction_score',
            'started_at',
            'last_message_at',
            'ended_at',
            'duration_seconds',
            'ai_summary',
        );

        $this->column_types = array(
            'id'                  => 'bigint',
            'total_messages'      => 'int',
            'bot_messages'        => 'int',
            'user_messages'       => 'int',
            'conversion_achieved' => 'tinyint',
            'satisfaction_score'  => 'int',
            'duration_seconds'    => 'int',
        );
    }

    /**
     * Get conversation by conversation_id.
     *
     * @param string $conversation_id The unique conversation ID.
     * @return object|null The conversation or null if not found.
     */
    public function get_by_conversation_id( $conversation_id ) {
        $conversation_id = sanitize_text_field( $conversation_id );

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

    /**
     * Get conversations by visitor ID.
     *
     * @param string $visitor_id The visitor ID.
     * @param int    $limit      Optional. Maximum number of results. Default 50.
     * @return array Array of conversations.
     */
    public function get_by_visitor_id( $visitor_id, $limit = 50 ) {
        $visitor_id = sanitize_text_field( $visitor_id );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        return $this->wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                "SELECT * FROM {$this->table_name} WHERE visitor_id = %s ORDER BY started_at DESC LIMIT %d",
                $visitor_id,
                absint( $limit )
            )
        );
    }

    /**
     * Get recent conversations.
     *
     * @param int $days  Optional. Number of days back. Default 7.
     * @param int $limit Optional. Maximum number of results. Default 100.
     * @return array Array of conversations.
     */
    public function get_recent( $days = 7, $limit = 100 ) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        return $this->wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                "SELECT * FROM {$this->table_name} 
                 WHERE started_at >= DATE_SUB(NOW(), INTERVAL %d DAY) 
                 ORDER BY started_at DESC 
                 LIMIT %d",
                absint( $days ),
                absint( $limit )
            )
        );
    }

    /**
     * Get conversations with conversions.
     *
     * @param int $limit Optional. Maximum number of results. Default 50.
     * @return array Array of conversations.
     */
    public function get_converted( $limit = 50 ) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        return $this->wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                "SELECT * FROM {$this->table_name} 
                 WHERE conversion_achieved = 1 
                 ORDER BY started_at DESC 
                 LIMIT %d",
                absint( $limit )
            )
        );
    }

    /**
     * Get active conversations (no ended_at).
     *
     * @param int $limit Optional. Maximum number of results. Default 50.
     * @return array Array of conversations.
     */
    public function get_active( $limit = 50 ) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        return $this->wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                "SELECT * FROM {$this->table_name} 
                 WHERE ended_at IS NULL 
                 ORDER BY last_message_at DESC 
                 LIMIT %d",
                absint( $limit )
            )
        );
    }

    /**
     * Add a message to the conversation.
     *
     * @param string $conversation_id The conversation ID.
     * @param array  $message         The message data (role, content, timestamp).
     * @return bool True on success, false on failure.
     */
    public function add_message( $conversation_id, $message ) {
        $conversation = $this->get_by_conversation_id( $conversation_id );

        if ( ! $conversation ) {
            return false;
        }

        // Decode existing session data
        $session_data = ! empty( $conversation->session_data ) 
            ? json_decode( $conversation->session_data, true ) 
            : array( 'messages' => array() );

        if ( ! is_array( $session_data ) ) {
            $session_data = array( 'messages' => array() );
        }

        if ( ! isset( $session_data['messages'] ) ) {
            $session_data['messages'] = array();
        }

        // Add the new message
        $session_data['messages'][] = array(
            'role'      => sanitize_text_field( $message['role'] ?? 'user' ),
            'content'   => sanitize_textarea_field( $message['content'] ?? '' ),
            'timestamp' => current_time( 'mysql' ),
        );

        // Update counts
        $total_messages = count( $session_data['messages'] );
        $bot_messages   = 0;
        $user_messages  = 0;

        foreach ( $session_data['messages'] as $msg ) {
            if ( $msg['role'] === 'assistant' || $msg['role'] === 'bot' ) {
                $bot_messages++;
            } else {
                $user_messages++;
            }
        }

        // Update the conversation
        return $this->update( $conversation->id, array(
            'session_data'    => wp_json_encode( $session_data ),
            'total_messages'  => $total_messages,
            'bot_messages'    => $bot_messages,
            'user_messages'   => $user_messages,
            'last_message_at' => current_time( 'mysql' ),
        ) );
    }

    /**
     * End a conversation.
     *
     * @param string $conversation_id The conversation ID.
     * @return bool True on success, false on failure.
     */
    public function end_conversation( $conversation_id ) {
        $conversation = $this->get_by_conversation_id( $conversation_id );

        if ( ! $conversation ) {
            return false;
        }

        // Calculate duration
        $started_at       = strtotime( $conversation->started_at );
        $ended_at         = current_time( 'timestamp' );
        $duration_seconds = $ended_at - $started_at;

        return $this->update( $conversation->id, array(
            'ended_at'         => current_time( 'mysql' ),
            'duration_seconds' => max( 0, $duration_seconds ),
        ) );
    }

    /**
     * Mark conversion achieved.
     *
     * @param string $conversation_id The conversation ID.
     * @return bool True on success, false on failure.
     */
    public function mark_converted( $conversation_id ) {
        $conversation = $this->get_by_conversation_id( $conversation_id );

        if ( ! $conversation ) {
            return false;
        }

        return $this->update( $conversation->id, array(
            'conversion_achieved' => 1,
        ) );
    }

    /**
     * Set satisfaction score.
     *
     * @param string $conversation_id The conversation ID.
     * @param int    $score           The satisfaction score (1-5).
     * @return bool True on success, false on failure.
     */
    public function set_satisfaction_score( $conversation_id, $score ) {
        $conversation = $this->get_by_conversation_id( $conversation_id );

        if ( ! $conversation ) {
            return false;
        }

        $score = max( 1, min( 5, absint( $score ) ) );

        return $this->update( $conversation->id, array(
            'satisfaction_score' => $score,
        ) );
    }

    /**
     * Get conversation statistics.
     *
     * @param int $days Optional. Number of days to look back. Default 30.
     * @return array Statistics array.
     */
    public function get_statistics( $days = 30 ) {
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
        $date_condition = $this->wpdb->prepare(
            'started_at >= DATE_SUB(NOW(), INTERVAL %d DAY)',
            absint( $days )
        );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $stats = $this->wpdb->get_row(
            "SELECT 
                COUNT(*) as total_conversations,
                SUM(CASE WHEN conversion_achieved = 1 THEN 1 ELSE 0 END) as conversions,
                AVG(total_messages) as avg_messages,
                AVG(duration_seconds) as avg_duration,
                AVG(satisfaction_score) as avg_satisfaction
             // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
             FROM {$this->table_name}
             WHERE {$date_condition}" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        );

        return array(
            'total_conversations' => (int) ( $stats->total_conversations ?? 0 ),
            'conversions'         => (int) ( $stats->conversions ?? 0 ),
            'conversion_rate'     => $stats->total_conversations > 0 
                ? round( ( $stats->conversions / $stats->total_conversations ) * 100, 2 ) 
                : 0,
            'avg_messages'        => round( (float) ( $stats->avg_messages ?? 0 ), 1 ),
            'avg_duration'        => (int) ( $stats->avg_duration ?? 0 ),
            'avg_satisfaction'    => round( (float) ( $stats->avg_satisfaction ?? 0 ), 1 ),
        );
    }

    /**
     * Delete old conversations.
     *
     * @param int $days Delete conversations older than this many days.
     * @return int Number of deleted rows.
     */
    public function delete_old( $days = 30 ) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        return $this->wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                "DELETE FROM {$this->table_name} WHERE started_at < DATE_SUB(NOW(), INTERVAL %d DAY)",
                absint( $days )
            )
        );
    }

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

        // Required fields for new conversations
        if ( null === $id ) {
            if ( empty( $data['conversation_id'] ) ) {
                $errors->add( 'missing_conversation_id', __( 'Conversation ID is required', 'boostbot' ) );
            }
            if ( empty( $data['visitor_id'] ) ) {
                $errors->add( 'missing_visitor_id', __( 'Visitor ID is required', 'boostbot' ) );
            }
        }

        // Validate satisfaction score if provided
        if ( isset( $data['satisfaction_score'] ) && null !== $data['satisfaction_score'] ) {
            $score = absint( $data['satisfaction_score'] );
            if ( $score < 1 || $score > 5 ) {
                $errors->add( 'invalid_score', __( 'Satisfaction score must be between 1 and 5', 'boostbot' ) );
            }
        }

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

        return true;
    }

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

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

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

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

        if ( isset( $data['session_data'] ) ) {
            $sanitized['session_data'] = is_string( $data['session_data'] ) 
                ? $data['session_data'] 
                : wp_json_encode( $data['session_data'] );
        }

        if ( isset( $data['context_data'] ) ) {
            $sanitized['context_data'] = is_string( $data['context_data'] ) 
                ? $data['context_data'] 
                : wp_json_encode( $data['context_data'] );
        }

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

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

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

        if ( isset( $data['widgets_used'] ) ) {
            $sanitized['widgets_used'] = is_string( $data['widgets_used'] ) 
                ? $data['widgets_used'] 
                : wp_json_encode( $data['widgets_used'] );
        }

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

        if ( isset( $data['satisfaction_score'] ) ) {
            $sanitized['satisfaction_score'] = max( 1, min( 5, absint( $data['satisfaction_score'] ) ) );
        }

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

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

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

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

        return $sanitized;
    }
}







