<?php
/**
 * Amazon S3 Handler for Media Offload
 *
 * Handles uploading and managing audio files in Amazon S3
 * using direct HTTP/REST API calls without external dependencies.
 *
 * @link       https://mementor.no
 * @since      1.5.0
 * @package    TTSWP
 * @subpackage Mementor_TTS/includes
 */

// Exit if accessed directly
if (!defined('ABSPATH')) {
    exit;
}

/**
 * S3 Handler Class
 *
 * Implements AWS Signature Version 4 for authenticated requests
 *
 * @package    TTSWP
 * @subpackage Mementor_TTS/includes
 */
class Mementor_TTS_S3_Handler {

    /**
     * AWS Access Key ID
     *
     * @var string
     */
    private $access_key;

    /**
     * AWS Secret Access Key
     *
     * @var string
     */
    private $secret_key;

    /**
     * AWS Region
     *
     * @var string
     */
    private $region;

    /**
     * S3 Bucket Name
     *
     * @var string
     */
    private $bucket;

    /**
     * Path prefix within bucket
     *
     * @var string
     */
    private $path_prefix;

    /**
     * Constructor
     *
     * @param array $config Configuration array with keys: access_key, secret_key, region, bucket, path
     */
    public function __construct($config = array()) {
        if (empty($config)) {
            // Load from options
            $this->access_key = get_option('mementor_tts_s3_access_key', '');
            $encrypted_secret_key = get_option('mementor_tts_s3_secret_key', '');

            // Decrypt the secret key if it exists
            if (!empty($encrypted_secret_key)) {
                require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
                $encryption = new Mementor_TTS_Encryption();
                $decrypted_key = $encryption->decrypt($encrypted_secret_key);

                // If decryption fails, assume it's an old unencrypted key
                if ($decrypted_key !== false) {
                    $this->secret_key = $decrypted_key;
                } else {
                    // Use as-is (backward compatibility for unencrypted keys)
                    $this->secret_key = $encrypted_secret_key;
                }
            } else {
                $this->secret_key = '';
            }

            $this->region = get_option('mementor_tts_s3_region', 'us-east-1');
            $this->bucket = get_option('mementor_tts_s3_bucket', '');
            $this->path_prefix = trim(get_option('mementor_tts_s3_path', 'tts-audio'), '/');
        } else {
            $this->access_key = isset($config['access_key']) ? $config['access_key'] : '';
            $this->secret_key = isset($config['secret_key']) ? $config['secret_key'] : '';
            $this->region = isset($config['region']) ? $config['region'] : 'us-east-1';
            $this->bucket = isset($config['bucket']) ? $config['bucket'] : '';
            $this->path_prefix = isset($config['path']) ? trim($config['path'], '/') : 'tts-audio';
        }
    }

    /**
     * Check if S3 is configured
     *
     * @return bool
     */
    public function is_configured() {
        return !empty($this->access_key) && !empty($this->secret_key) && !empty($this->bucket);
    }

    /**
     * Test S3 connection by listing bucket
     *
     * @return array Result array with 'success' and 'message' keys
     */
    public function test_connection() {
        if (!$this->is_configured()) {
            return array(
                'success' => false,
                'message' => __('S3 configuration incomplete. Please fill in all required fields.', 'text-to-speech-tts')
            );
        }

        try {
            // Try to list objects (HEAD bucket request)
            $endpoint = $this->get_endpoint();
            $url = 'https://' . $endpoint . '/';

            $headers = $this->get_auth_headers('GET', '/', '', array());

            $response = wp_remote_get($url, array(
                'headers' => $headers,
                'timeout' => 10,
                'sslverify' => true
            ));

            if (is_wp_error($response)) {
                return array(
                    'success' => false,
                    'message' => sprintf(
                        /* translators: %s: Underlying network or HTTP error message from WordPress. */
                        __('Connection failed: %s', 'text-to-speech-tts'),
                        $response->get_error_message()
                    )
                );
            }

            $status_code = wp_remote_retrieve_response_code($response);

            if ($status_code === 200 || $status_code === 204) {
                return array(
                    'success' => true,
                    'message' => sprintf(
                        /* translators: 1: S3 bucket name. 2: AWS region code (e.g. "eu-west-1"). */
                        __('Successfully connected to bucket "%1$s" in region "%2$s"', 'text-to-speech-tts'),
                        $this->bucket,
                        $this->region
                    )
                );
            } elseif ($status_code === 403) {
                return array(
                    'success' => false,
                    'message' => __('Access denied. Please check your credentials and bucket permissions.', 'text-to-speech-tts')
                );
            } elseif ($status_code === 404) {
                return array(
                    'success' => false,
                    'message' => sprintf(
                        /* translators: 1: S3 bucket name. 2: AWS region code (e.g. "eu-west-1"). */
                        __('Bucket "%1$s" not found in region "%2$s". Please check the bucket name and region.', 'text-to-speech-tts'),
                        $this->bucket,
                        $this->region
                    )
                );
            } else {
                $body = wp_remote_retrieve_body($response);
                return array(
                    'success' => false,
                    'message' => sprintf(
                        /* translators: 1: HTTP status code from S3. 2: Error message parsed from the S3 response body. */
                        __('S3 error (code %1$d): %2$s', 'text-to-speech-tts'),
                        $status_code,
                        $this->extract_error_message($body)
                    )
                );
            }
        } catch (Exception $e) {
            return array(
                'success' => false,
                'message' => sprintf(
                    /* translators: %s: PHP exception message. */
                    __('Exception: %s', 'text-to-speech-tts'),
                    $e->getMessage()
                )
            );
        }
    }

    /**
     * Upload file to S3
     *
     * @param string $file_path Local file path
     * @param string $s3_key S3 object key (filename in bucket)
     * @param string $content_type MIME type
     * @return array|WP_Error Result array or WP_Error on failure
     */
    public function upload_file($file_path, $s3_key, $content_type = 'audio/mpeg') {
        if (!$this->is_configured()) {
            return new WP_Error('s3_not_configured', __('S3 is not configured', 'text-to-speech-tts'));
        }

        if (!file_exists($file_path)) {
            return new WP_Error('file_not_found', __('File not found', 'text-to-speech-tts'));
        }

        // Prepend path prefix
        if (!empty($this->path_prefix)) {
            $s3_key = $this->path_prefix . '/' . ltrim($s3_key, '/');
        }

        // Read file content
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Raw bytes required for S3 PUT body; WP_Filesystem's get_contents is inappropriate for binary uploads.
        $file_content = file_get_contents($file_path);
        if ($file_content === false) {
            return new WP_Error('file_read_error', __('Could not read file', 'text-to-speech-tts'));
        }

        // Build URL
        $endpoint = $this->get_endpoint();
        $url = 'https://' . $endpoint . '/' . $s3_key;

        // Build headers
        // Note: ACL header removed to support buckets with ACLs disabled (modern S3 best practice)
        // Use bucket policies to control public access instead
        $headers = $this->get_auth_headers('PUT', '/' . $s3_key, $file_content, array(
            'Content-Type' => $content_type
        ));

        // Upload file
        $response = wp_remote_request($url, array(
            'method' => 'PUT',
            'headers' => $headers,
            'body' => $file_content,
            'timeout' => 60,
            'sslverify' => true
        ));

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

        $status_code = wp_remote_retrieve_response_code($response);

        if ($status_code === 200 || $status_code === 204) {
            // Success - return the public URL
            $public_url = 'https://' . $this->bucket . '.s3.' . $this->region . '.amazonaws.com/' . $s3_key;

            return array(
                'success' => true,
                'url' => $public_url,
                'key' => $s3_key
            );
        } else {
            $body = wp_remote_retrieve_body($response);
            return new WP_Error(
                's3_upload_error',
                sprintf(
                    /* translators: 1: HTTP status code from S3 upload. 2: Error message parsed from the S3 response body. */
                    __('S3 upload failed (code %1$d): %2$s', 'text-to-speech-tts'),
                    $status_code,
                    $this->extract_error_message($body)
                )
            );
        }
    }

    /**
     * Delete file from S3
     *
     * @param string $s3_key S3 object key
     * @param bool $use_full_key If true, use the key as-is without prepending path prefix (for URLs)
     * @return bool True on success
     */
    public function delete_file($s3_key, $use_full_key = false) {
        error_log('S3 Handler: delete_file called with key=' . $s3_key . ', use_full_key=' . ($use_full_key ? 'true' : 'false'));

        if (!$this->is_configured()) {
            error_log('S3 Handler: Not configured');
            return false;
        }

        // Prepend path prefix if not already there (unless using full key from URL)
        if (!$use_full_key && !empty($this->path_prefix) && strpos($s3_key, $this->path_prefix . '/') !== 0) {
            $s3_key = $this->path_prefix . '/' . ltrim($s3_key, '/');
        }

        error_log('S3 Handler: Final S3 key to delete: ' . $s3_key);

        $endpoint = $this->get_endpoint();
        $url = 'https://' . $endpoint . '/' . $s3_key;

        error_log('S3 Handler: DELETE URL: ' . $url);

        $headers = $this->get_auth_headers('DELETE', '/' . $s3_key, '', array());

        $response = wp_remote_request($url, array(
            'method' => 'DELETE',
            'headers' => $headers,
            'timeout' => 30,
            'sslverify' => true
        ));

        if (is_wp_error($response)) {
            error_log('S3 Handler: WP_Error: ' . $response->get_error_message());
            return false;
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        error_log('S3 Handler: Response status code: ' . $status_code);
        error_log('S3 Handler: Response body: ' . $response_body);

        return ($status_code === 200 || $status_code === 204);
    }

    /**
     * Get S3 endpoint hostname
     *
     * @return string
     */
    private function get_endpoint() {
        return $this->bucket . '.s3.' . $this->region . '.amazonaws.com';
    }

    /**
     * Generate authorization headers using AWS Signature Version 4
     *
     * @param string $method HTTP method (GET, PUT, DELETE, etc.)
     * @param string $uri Request URI
     * @param string $payload Request body
     * @param array $extra_headers Additional headers
     * @return array Headers array
     */
    private function get_auth_headers($method, $uri, $payload = '', $extra_headers = array()) {
        $service = 's3';
        $timestamp = gmdate('Ymd\THis\Z');
        $date = gmdate('Ymd');

        // Hash payload
        $payload_hash = hash('sha256', $payload);

        // Prepare headers
        $headers = array_merge(array(
            'Host' => $this->get_endpoint(),
            'x-amz-date' => $timestamp,
            'x-amz-content-sha256' => $payload_hash
        ), $extra_headers);

        // Canonical headers
        $canonical_headers = '';
        $signed_headers = '';
        ksort($headers);
        $signed_header_keys = array();

        foreach ($headers as $key => $value) {
            $lowercase_key = strtolower($key);
            $canonical_headers .= $lowercase_key . ':' . trim($value) . "\n";
            $signed_header_keys[] = $lowercase_key;
        }

        $signed_headers = implode(';', $signed_header_keys);

        // Canonical request
        $canonical_request = $method . "\n" .
                            $uri . "\n" .
                            '' . "\n" . // Query string (empty)
                            $canonical_headers . "\n" .
                            $signed_headers . "\n" .
                            $payload_hash;

        // String to sign
        $credential_scope = $date . '/' . $this->region . '/' . $service . '/aws4_request';
        $string_to_sign = 'AWS4-HMAC-SHA256' . "\n" .
                         $timestamp . "\n" .
                         $credential_scope . "\n" .
                         hash('sha256', $canonical_request);

        // Calculate signature
        $signing_key = $this->get_signing_key($date, $this->region, $service);
        $signature = hash_hmac('sha256', $string_to_sign, $signing_key);

        // Authorization header
        $authorization = 'AWS4-HMAC-SHA256 ' .
                        'Credential=' . $this->access_key . '/' . $credential_scope . ', ' .
                        'SignedHeaders=' . $signed_headers . ', ' .
                        'Signature=' . $signature;

        $headers['Authorization'] = $authorization;

        return $headers;
    }

    /**
     * Get signing key for AWS Signature Version 4
     *
     * @param string $date Date in Ymd format
     * @param string $region AWS region
     * @param string $service AWS service
     * @return string Binary signing key
     */
    private function get_signing_key($date, $region, $service) {
        $kDate = hash_hmac('sha256', $date, 'AWS4' . $this->secret_key, true);
        $kRegion = hash_hmac('sha256', $region, $kDate, true);
        $kService = hash_hmac('sha256', $service, $kRegion, true);
        $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);

        return $kSigning;
    }

    /**
     * Extract error message from S3 XML response
     *
     * @param string $xml_body XML response body
     * @return string Error message
     */
    private function extract_error_message($xml_body) {
        if (empty($xml_body)) {
            return __('Unknown error', 'text-to-speech-tts');
        }

        // Try to parse XML
        $previous_errors = libxml_use_internal_errors(true);
        $xml = simplexml_load_string($xml_body);
        libxml_use_internal_errors($previous_errors);

        if ($xml !== false && isset($xml->Message)) {
            return (string) $xml->Message;
        }

        // Fallback: return truncated body
        return substr($xml_body, 0, 200);
    }
}
