<?php
/**
 * SaaS API Client — handles all communication with app.ttswp.com.
 * Replaces direct ElevenLabs calls with SaaS-proxied synthesis.
 */

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

class Mementor_TTS_Api_Client {

    private static $instance = null;

    /**
     * Per-request stash of the last synthesize payload so speech-builder's
     * log_generation can include it in the remote-debug event. Gated by the
     * "Include Content Samples" setting; stays null when that's off.
     */
    public static $last_tts_payload = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Synthesize text to speech via the SaaS API.
     * Includes one automatic retry on network failure.
     *
     * @param string $text       The text or SSML to synthesize.
     * @param string $voice_id   ElevenLabs voice ID.
     * @param array  $options    Optional: model_id, language_code, stability, similarity_boost, style, speed, use_speaker_boost, filename.
     * @return array|WP_Error    Response array with audio_url, credits_charged, credits_remaining — or WP_Error.
     */
    public function synthesize( $text, $voice_id, $options = array() ) {
        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            return new WP_Error( 'not_connected', __( 'Site is not connected to TTSWP.', 'text-to-speech-tts' ) );
        }

        if ( get_option( 'mementor_tts_remote_debug_include_content', '0' ) === '1' ) {
            self::$last_tts_payload = mb_substr( (string) $text, 0, 2000 );
        }

        $api_url = Mementor_TTS_Connect::get_app_url() . '/api/v1/synthesize';

        $body = array_merge( array(
            'text'       => $text,
            'voice'      => $voice_id,
            'regenerate' => true,
        ), array_filter( array(
            'model_id'          => $options['model_id'] ?? null,
            'language_code'     => $options['language_code'] ?? null,
            'stability'         => $options['stability'] ?? null,
            'similarity_boost'  => $options['similarity_boost'] ?? null,
            'style'             => $options['style'] ?? null,
            'speed'             => $options['speed'] ?? null,
            'use_speaker_boost' => $options['use_speaker_boost'] ?? null,
            'filename'          => $options['filename'] ?? null,
            'output_format'     => $options['output_format'] ?? null,
        ) ) );

        $args = array(
            'timeout' => 60,
            'headers' => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
                'Accept'           => 'application/json',
                'Content-Type'     => 'application/json',
            ),
            'body' => wp_json_encode( $body ),
        );

        // First attempt
        $response = wp_remote_post( $api_url, $args );

        // Retry once on network failure after 2-3 second delay
        if ( is_wp_error( $response ) ) {
            sleep( 2 );
            $response = wp_remote_post( $api_url, $args );
        }

        if ( is_wp_error( $response ) ) {
            return new WP_Error( 'api_error', __( 'Could not connect to TTSWP API.', 'text-to-speech-tts' ) );
        }

        $code = wp_remote_retrieve_response_code( $response );
        $data = json_decode( wp_remote_retrieve_body( $response ), true );

        // Auto-disconnect if token is invalid
        if ( 401 === $code ) {
            Mementor_TTS_Connect::disconnect();
        }

        if ( 200 !== $code ) {
            $message = $data['error'] ?? $data['message'] ?? __( 'Audio generation failed.', 'text-to-speech-tts' );
            return new WP_Error( 'synthesis_failed', $message, array( 'status' => $code, 'data' => $data ) );
        }

        // Update credit balance transient for low-credit banner
        if ( isset( $data['credits_remaining'] ) ) {
            set_transient( 'tts_credits_balance', (int) $data['credits_remaining'], DAY_IN_SECONDS );
        }

        return $data;
    }

    /**
     * Save audio from a synthesize response to the appropriate location.
     *
     * Paid plans: store the CloudFront URL directly in post meta.
     * Free plan:  download the temporary URL to wp-content/uploads/text-to-speech-tts/
     *             and store local URL. (Pre-3.2.14 versions wrote to /uploads/tts/;
     *             that path remains supported as a read fallback in the resolver.)
     *
     * @param int   $post_id   The WordPress post ID.
     * @param array $response  The response array from synthesize().
     * @param string $language_code Optional language code for multilingual support.
     * @return string|WP_Error The final audio URL (local or CDN) or WP_Error.
     */
    public function save_audio( $post_id, $response, $language_code = '' ) {
        $audio_url = $response['url'] ?? '';

        if ( empty( $audio_url ) ) {
            return new WP_Error( 'no_audio_url', __( 'No audio URL in response.', 'text-to-speech-tts' ) );
        }

        if ( ttswp_has_paid_plan() ) {
            // Paid plan: store CDN URL directly
            $final_url = $audio_url;
        } else {
            // Free plan: download temp URL to local uploads
            $final_url = $this->download_to_uploads( $audio_url, $post_id );
            if ( is_wp_error( $final_url ) ) {
                return $final_url;
            }
        }

        // Update post meta
        update_post_meta( $post_id, '_mementor_tts_has_audio', true );
        update_post_meta( $post_id, '_mementor_tts_last_generated', current_time( 'timestamp' ) );

        if ( ! empty( $language_code ) ) {
            update_post_meta( $post_id, '_mementor_tts_audio_url_' . sanitize_key( $language_code ), $final_url );
        } else {
            update_post_meta( $post_id, '_mementor_tts_audio_url', $final_url );
        }

        return $final_url;
    }

    /**
     * Download an audio file from a temporary URL and save to
     * wp-content/uploads/text-to-speech-tts/. Files written before 3.2.14 live
     * in /uploads/tts/ and are still read by the resolver as a fallback.
     *
     * @param string $url      The temporary URL to download from.
     * @param int    $post_id  The post ID (used for filename).
     * @return string|WP_Error Local URL or WP_Error on failure.
     */
    private function download_to_uploads( $url, $post_id ) {
        $response = wp_remote_get( $url, array( 'timeout' => 30 ) );

        if ( is_wp_error( $response ) ) {
            return new WP_Error( 'download_failed', __( 'Failed to download audio file.', 'text-to-speech-tts' ) );
        }

        $code = wp_remote_retrieve_response_code( $response );
        if ( 200 !== $code ) {
            /* translators: %d: HTTP status code returned by the audio download request. */
            return new WP_Error( 'download_failed', sprintf( __( 'Audio download returned HTTP %d.', 'text-to-speech-tts' ), $code ) );
        }

        $audio_data = wp_remote_retrieve_body( $response );
        if ( empty( $audio_data ) ) {
            return new WP_Error( 'empty_audio', __( 'Downloaded audio file is empty.', 'text-to-speech-tts' ) );
        }

        // Create uploads directory (shared with paid-plan and shortcode audio).
        $upload_dir = wp_upload_dir();
        $tts_dir    = $upload_dir['basedir'] . '/text-to-speech-tts';
        if ( ! file_exists( $tts_dir ) ) {
            wp_mkdir_p( $tts_dir );
        }

        $filename = 'tts-' . $post_id . '-' . time() . '.mp3';
        $filepath = $tts_dir . '/' . $filename;

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Called from synchronous REST-client path where WP_Filesystem may not be initialized.
        $written = file_put_contents( $filepath, $audio_data );
        if ( false === $written ) {
            return new WP_Error( 'save_failed', __( 'Could not save audio file to uploads.', 'text-to-speech-tts' ) );
        }

        return $upload_dir['baseurl'] . '/text-to-speech-tts/' . $filename;
    }

    /**
     * Fetch available voices from the SaaS API (proxied from ElevenLabs).
     * Cached locally for 1 hour.
     *
     * @return array|WP_Error Voices data or WP_Error.
     */
    public function get_voices() {
        $cached = get_transient( 'tts_saas_voices' );
        if ( false !== $cached ) {
            return $cached;
        }

        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            return new WP_Error( 'not_connected', __( 'Site is not connected to TTSWP.', 'text-to-speech-tts' ) );
        }

        $api_url = Mementor_TTS_Connect::get_app_url() . '/api/v1/voices';

        $response = wp_remote_get( $api_url, array(
            'timeout' => 30,
            'headers' => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
                'Accept'           => 'application/json',
            ),
        ) );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $data = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( 200 !== $code || ! is_array( $data ) ) {
            return new WP_Error( 'voices_failed', __( 'Failed to fetch voices.', 'text-to-speech-tts' ) );
        }

        set_transient( 'tts_saas_voices', $data, HOUR_IN_SECONDS );

        return $data;
    }

    /**
     * Ask the SaaS to delete an audio file from S3 and invalidate its
     * CloudFront path. Accepts either an S3 key, a bare filename, or the
     * full CDN URL; the server normalizes and verifies ownership.
     *
     * @param string $path_or_url S3 key, filename, or full CDN URL.
     * @return true|WP_Error
     */
    public function delete_audio( $path_or_url ) {
        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            return new WP_Error( 'not_connected', __( 'Site is not connected to TTSWP.', 'text-to-speech-tts' ) );
        }

        $api_url = Mementor_TTS_Connect::get_app_url() . '/api/v1/audio';

        $response = wp_remote_request( $api_url, array(
            'method'  => 'DELETE',
            'timeout' => 30,
            'headers' => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
                'Accept'           => 'application/json',
                'Content-Type'     => 'application/json',
            ),
            'body'    => wp_json_encode( array( 'filename' => $path_or_url ) ),
        ) );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        if ( 200 !== $code ) {
            return new WP_Error( 'delete_failed', sprintf( 'SaaS delete returned HTTP %d', $code ) );
        }

        return true;
    }

    /**
     * Fire-and-forget POST of a debug event to the SaaS. Silent on every error
     * — never throws, never blocks the page. The endpoint is gated by the site
     * owner's Remote Debug toggle + site connection.
     *
     * @param array $event {
     *   @type string $event_type      e.g. 'generate_start', 'generate_error'
     *   @type string $severity        'debug'|'info'|'warning'|'error'
     *   @type string $message         short human string, max 500 chars
     *   @type array  $context         arbitrary associative array (stringly keyed)
     *   @type int    $post_id         optional post id
     * }
     * @return void
     */
    public function send_remote_debug_event( array $event ) {
        if ( get_option( 'mementor_tts_remote_debug_enabled', '0' ) !== '1' ) {
            return;
        }

        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            return;
        }

        $body = array(
            'event_type'     => isset( $event['event_type'] ) ? (string) $event['event_type'] : 'other',
            'severity'       => isset( $event['severity'] ) ? (string) $event['severity'] : 'debug',
            'message'        => isset( $event['message'] ) ? mb_substr( (string) $event['message'], 0, 500 ) : '',
            'context'        => isset( $event['context'] ) && is_array( $event['context'] ) ? $event['context'] : null,
            'plugin_version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
            'post_id'        => isset( $event['post_id'] ) ? (string) $event['post_id'] : null,
        );

        $api_url = Mementor_TTS_Connect::get_app_url() . '/api/v1/site-debug-log';

        $args = array(
            'timeout'  => 3,
            'blocking' => false,
            'headers'  => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => $body['plugin_version'],
                'Accept'           => 'application/json',
                'Content-Type'     => 'application/json',
            ),
            'body'     => wp_json_encode( $body ),
        );

        wp_remote_post( $api_url, $args );

        set_transient( 'mementor_tts_remote_debug_last_event', time(), DAY_IN_SECONDS );
    }
}
