<?php
require_once __DIR__ . '/../config/config.php';

/**
 * Stores third-party API integration settings (DeepSeek, Claude/Anthropic, OpenAI, or any
 * custom API) as named entries so admins can add as many as needed from Settings > APIs.
 * API keys are encrypted at rest using the same AES-256-CBC scheme as MailboxService.
 */
class ApiIntegrationService {
    const PRESETS = [
        'deepseek' => ['label' => 'DeepSeek', 'base_url' => 'https://api.deepseek.com', 'model' => 'deepseek-chat'],
        'anthropic' => ['label' => 'Claude (Anthropic)', 'base_url' => 'https://api.anthropic.com', 'model' => 'claude-opus-4-8'],
        'openai' => ['label' => 'OpenAI (ChatGPT)', 'base_url' => 'https://api.openai.com/v1', 'model' => 'gpt-4o'],
        'custom' => ['label' => 'Custom / Other', 'base_url' => '', 'model' => ''],
    ];

    private $conn;
    private $integrationId;
    private $settings;

    public function __construct(PDO $conn, $integrationId) {
        $this->conn = $conn;
        $this->integrationId = (int)$integrationId;
        self::ensureTable($conn);
        $this->settings = $this->loadSettings();
    }

    public function getIntegrationId() {
        return $this->integrationId;
    }

    // ---------------------------------------------------------------
    // Static helpers
    // ---------------------------------------------------------------

    public static function ensureTable(PDO $conn) {
        $conn->exec(
            'CREATE TABLE IF NOT EXISTS api_integrations (' .
            'integration_id INT AUTO_INCREMENT PRIMARY KEY,' .
            'provider_key VARCHAR(50) NOT NULL DEFAULT "custom",' .
            'provider_label VARCHAR(100) NOT NULL,' .
            'api_key_encrypted TEXT NULL,' .
            'api_base_url VARCHAR(255) NULL,' .
            'model_name VARCHAR(100) NULL,' .
            'is_enabled TINYINT(1) NOT NULL DEFAULT 1,' .
            'notes VARCHAR(500) NULL,' .
            'created_by INT NULL,' .
            'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,' .
            'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' .
            ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci'
        );
    }

    public static function listAccounts(PDO $conn) {
        self::ensureTable($conn);
        $stmt = $conn->query(
            'SELECT integration_id, provider_key, provider_label, api_base_url, model_name, ' .
            'is_enabled, (api_key_encrypted IS NOT NULL AND api_key_encrypted <> "") AS has_key, updated_at ' .
            'FROM api_integrations ORDER BY provider_label ASC'
        );
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public static function createAccount(PDO $conn, array $data, $userId) {
        self::ensureTable($conn);

        $providerKey = self::normalizeProviderKey($data['provider_key'] ?? 'custom');
        $label = trim((string)($data['provider_label'] ?? ''));
        if ($label === '') {
            $label = self::PRESETS[$providerKey]['label'] ?? 'Custom / Other';
        }
        if (strlen($label) > 100) {
            throw new InvalidArgumentException('Integration name is too long.');
        }

        $apiKey = (string)($data['api_key'] ?? '');
        $encryptedKey = $apiKey !== '' ? self::encrypt($apiKey) : null;

        $stmt = $conn->prepare(
            'INSERT INTO api_integrations ' .
            '(provider_key, provider_label, api_key_encrypted, api_base_url, model_name, is_enabled, notes, created_by) ' .
            'VALUES (:provider_key, :provider_label, :api_key_encrypted, :api_base_url, :model_name, :is_enabled, :notes, :created_by)'
        );
        $stmt->execute([
            ':provider_key' => $providerKey,
            ':provider_label' => $label,
            ':api_key_encrypted' => $encryptedKey,
            ':api_base_url' => self::normalizeOptionalText($data['api_base_url'] ?? '', 255),
            ':model_name' => self::normalizeOptionalText($data['model_name'] ?? '', 100),
            ':is_enabled' => !empty($data['is_enabled']) ? 1 : 0,
            ':notes' => self::normalizeOptionalText($data['notes'] ?? '', 500),
            ':created_by' => $userId ? (int)$userId : null,
        ]);

        return (int)$conn->lastInsertId();
    }

    public static function deleteAccount(PDO $conn, $integrationId) {
        self::ensureTable($conn);
        $stmt = $conn->prepare('DELETE FROM api_integrations WHERE integration_id = :integration_id');
        $stmt->execute([':integration_id' => (int)$integrationId]);
    }

    private static function normalizeProviderKey($value) {
        $value = strtolower(trim((string)$value));
        return array_key_exists($value, self::PRESETS) ? $value : 'custom';
    }

    private static function normalizeOptionalText($value, $maxLength) {
        $value = trim((string)$value);
        if (strlen($value) > $maxLength) {
            throw new InvalidArgumentException('One of the API integration fields is too long.');
        }
        return $value !== '' ? $value : null;
    }

    // ---------------------------------------------------------------
    // Instance settings
    // ---------------------------------------------------------------

    private function loadSettings() {
        $defaults = [
            'provider_key' => 'custom',
            'provider_label' => '',
            'api_key' => '',
            'api_base_url' => '',
            'model_name' => '',
            'is_enabled' => true,
            'notes' => '',
        ];

        if ($this->integrationId <= 0) {
            return $defaults;
        }

        $stmt = $this->conn->prepare('SELECT * FROM api_integrations WHERE integration_id = :integration_id');
        $stmt->execute([':integration_id' => $this->integrationId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            return $defaults;
        }

        $key = $row['api_key_encrypted'];
        return [
            'provider_key' => $row['provider_key'],
            'provider_label' => $row['provider_label'],
            'api_key' => ($key !== '' && $key !== null) ? self::decrypt($key) : '',
            'api_base_url' => (string)$row['api_base_url'],
            'model_name' => (string)$row['model_name'],
            'is_enabled' => (bool)$row['is_enabled'],
            'notes' => (string)$row['notes'],
        ];
    }

    /** Settings safe to render back to the admin UI (API key masked). */
    public function getDisplaySettings() {
        $safe = $this->settings;
        $safe['api_key'] = $this->settings['api_key'] !== '' ? str_repeat('*', 12) : '';
        return $safe;
    }

    /** Unmasked settings (including the decrypted API key) for server-side use only, e.g. testing the connection. */
    public function getRawSettings() {
        return $this->settings;
    }

    public function saveSettings(array $data, $userId) {
        if ($this->integrationId <= 0) {
            throw new InvalidArgumentException('Select an API integration to update.');
        }

        $providerKey = self::normalizeProviderKey($data['provider_key'] ?? $this->settings['provider_key']);
        $label = trim((string)($data['provider_label'] ?? $this->settings['provider_label']));
        if ($label === '') {
            throw new InvalidArgumentException('Integration name is required.');
        }
        if (strlen($label) > 100) {
            throw new InvalidArgumentException('Integration name is too long.');
        }

        // Keep the previously stored key if the admin left the masked field untouched.
        $apiKey = $data['api_key'] ?? '';
        $keepExistingKey = ($apiKey === '' || preg_match('/^\*+$/', $apiKey));

        $sql = 'UPDATE api_integrations SET provider_key = :provider_key, provider_label = :provider_label, ' .
            ($keepExistingKey ? '' : 'api_key_encrypted = :api_key_encrypted, ') .
            'api_base_url = :api_base_url, model_name = :model_name, is_enabled = :is_enabled, notes = :notes ' .
            'WHERE integration_id = :integration_id';

        $stmt = $this->conn->prepare($sql);
        $stmt->bindValue(':provider_key', $providerKey);
        $stmt->bindValue(':provider_label', $label);
        if (!$keepExistingKey) {
            $stmt->bindValue(':api_key_encrypted', $apiKey !== '' ? self::encrypt($apiKey) : null);
        }
        $stmt->bindValue(':api_base_url', self::normalizeOptionalText($data['api_base_url'] ?? '', 255));
        $stmt->bindValue(':model_name', self::normalizeOptionalText($data['model_name'] ?? '', 100));
        $stmt->bindValue(':is_enabled', !empty($data['is_enabled']) ? 1 : 0, PDO::PARAM_INT);
        $stmt->bindValue(':notes', self::normalizeOptionalText($data['notes'] ?? '', 500));
        $stmt->bindValue(':integration_id', $this->integrationId, PDO::PARAM_INT);
        $stmt->execute();

        $this->settings = $this->loadSettings();
    }

    // ---------------------------------------------------------------
    // Connection test
    // ---------------------------------------------------------------

    /** Sends a minimal live request to the provider to verify the stored credentials work. */
    public static function testConnection($providerKey, $apiKey, $baseUrl, $modelName) {
        $providerKey = self::normalizeProviderKey($providerKey);
        $apiKey = trim((string)$apiKey);
        if ($apiKey === '') {
            return ['success' => false, 'error' => 'No API key is set for this integration.'];
        }

        $baseUrl = rtrim($baseUrl !== '' ? $baseUrl : (self::PRESETS[$providerKey]['base_url'] ?? ''), '/');
        if ($baseUrl === '') {
            return ['success' => false, 'error' => 'No API base URL is configured.'];
        }
        $model = trim((string)$modelName) !== '' ? trim($modelName) : (self::PRESETS[$providerKey]['model'] ?? '');

        try {
            switch ($providerKey) {
                case 'anthropic':
                    return self::testAnthropic($baseUrl, $apiKey, $model);
                case 'openai':
                case 'deepseek':
                    return self::testOpenAiCompatible($baseUrl, $apiKey, $model, self::PRESETS[$providerKey]['label']);
                default:
                    return self::testGenericReachability($baseUrl, $apiKey);
            }
        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    private static function testAnthropic($baseUrl, $apiKey, $model) {
        $model = $model !== '' ? $model : 'claude-opus-4-8';
        $payload = json_encode([
            'model' => $model,
            'max_tokens' => 1,
            'messages' => [['role' => 'user', 'content' => 'Hi']],
        ]);

        $response = self::curlRequest($baseUrl . '/v1/messages', 'POST', $payload, [
            'x-api-key: ' . $apiKey,
            'anthropic-version: 2023-06-01',
            'content-type: application/json',
        ]);

        return self::interpretResponse($response, 'Claude (Anthropic)');
    }

    private static function testOpenAiCompatible($baseUrl, $apiKey, $model, $label) {
        $payload = json_encode([
            'model' => $model !== '' ? $model : 'gpt-4o',
            'max_tokens' => 1,
            'messages' => [['role' => 'user', 'content' => 'Hi']],
        ]);

        $response = self::curlRequest($baseUrl . '/chat/completions', 'POST', $payload, [
            'Authorization: Bearer ' . $apiKey,
            'content-type: application/json',
        ]);

        return self::interpretResponse($response, $label);
    }

    /** Custom/other providers: no known request shape, so just confirm the host + key are reachable. */
    private static function testGenericReachability($baseUrl, $apiKey) {
        $response = self::curlRequest($baseUrl, 'GET', null, [
            'Authorization: Bearer ' . $apiKey,
        ]);
        return self::interpretResponse($response, 'API', true);
    }

    private static function curlRequest($url, $method, $payload, $headers) {
        if (!function_exists('curl_init')) {
            throw new Exception('The PHP cURL extension is not enabled on this server.');
        }
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT => 15,
            CURLOPT_CONNECTTIMEOUT => 8,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        if ($payload !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        }
        $body = curl_exec($ch);
        $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        return ['body' => $body, 'http_code' => $httpCode, 'curl_error' => $error];
    }

    private static function interpretResponse(array $response, $label, $reachabilityOnly = false) {
        if ($response['curl_error']) {
            return ['success' => false, 'error' => "Connection failed: {$response['curl_error']}"];
        }

        $httpCode = $response['http_code'];

        if ($httpCode >= 200 && $httpCode < 300) {
            return ['success' => true, 'message' => "$label connection successful (HTTP $httpCode)."];
        }

        if ($httpCode === 401 || $httpCode === 403) {
            return ['success' => false, 'error' => "Authentication failed (HTTP $httpCode). Check the API key."];
        }

        if ($reachabilityOnly && $httpCode > 0 && $httpCode < 500) {
            return [
                'success' => true,
                'message' => "Reached the $label host (HTTP $httpCode). Custom providers can't be fully " .
                    "verified with a chat request, so this only confirms the host and key were sent successfully.",
            ];
        }

        $decoded = json_decode((string)$response['body'], true);
        $errMsg = is_array($decoded) ? ($decoded['error']['message'] ?? $decoded['message'] ?? null) : null;

        return ['success' => false, 'error' => $errMsg ?: "Request failed (HTTP $httpCode)."];
    }

    // ---------------------------------------------------------------
    // Encryption helpers (AES-256-CBC using config/config.php ENCRYPTION_KEY)
    // ---------------------------------------------------------------

    private static function requireOpenssl() {
        if (!extension_loaded('openssl')) {
            throw new Exception('The PHP OpenSSL extension is not enabled on this server. Enable "php_openssl" to store API keys securely.');
        }
    }

    private static function encrypt($plaintext) {
        self::requireOpenssl();
        $key = hash('sha256', ENCRYPTION_KEY, true);
        $iv = openssl_random_pseudo_bytes(16);
        $ciphertext = openssl_encrypt($plaintext, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return base64_encode($iv . $ciphertext);
    }

    private static function decrypt($encoded) {
        self::requireOpenssl();
        $key = hash('sha256', ENCRYPTION_KEY, true);
        $raw = base64_decode($encoded);
        if ($raw === false || strlen($raw) < 17) {
            return '';
        }
        $iv = substr($raw, 0, 16);
        $ciphertext = substr($raw, 16);
        $plaintext = openssl_decrypt($ciphertext, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return $plaintext === false ? '' : $plaintext;
    }
}
