<?php

declare(strict_types=1);

namespace App\Models;

use App\Core\Database;

final class IntegrationModel
{
    private const ENCRYPTION_CIPHER = 'aes-256-cbc';

    public function all(): array
    {
        return Database::connection()->query('SELECT * FROM integrations ORDER BY name')->fetchAll();
    }

    public function findBySlug(string $slug): ?array
    {
        $stmt = Database::connection()->prepare('SELECT * FROM integrations WHERE slug = :slug');
        $stmt->execute(['slug' => $slug]);
        $row = $stmt->fetch();

        return $row === false ? null : $row;
    }

    /** Decrypted credentials as an associative array (e.g. ['api_key' => '...'] or OAuth ['access_token'=>..,'refresh_token'=>..,'expires_at'=>..]). */
    public function credentialsFor(string $slug): ?array
    {
        $row = $this->findBySlug($slug);
        if ($row === null || $row['credentials_encrypted'] === null) {
            return null;
        }

        $json = $this->decrypt($row['credentials_encrypted']);

        return $json !== null ? (json_decode($json, true) ?? null) : null;
    }

    public function connect(string $slug, array $credentials, int $userId): bool
    {
        $encrypted = $this->encrypt(json_encode($credentials));

        $stmt = Database::connection()->prepare(
            "UPDATE integrations SET status = 'connected', credentials_encrypted = :creds, connected_by = :user, connected_at = NOW(), updated_at = NOW()
             WHERE slug = :slug"
        );

        return $stmt->execute(['creds' => $encrypted, 'user' => $userId, 'slug' => $slug]);
    }

    /** Stores credentials without flipping status to 'connected' — for saving an OAuth client id/secret ahead of the actual authorization handshake, so the UI doesn't claim "Connected" before a real token exists. */
    public function saveConfigOnly(string $slug, array $credentials): bool
    {
        $encrypted = $this->encrypt(json_encode($credentials));

        $stmt = Database::connection()->prepare(
            'UPDATE integrations SET credentials_encrypted = :creds, updated_at = NOW() WHERE slug = :slug'
        );

        return $stmt->execute(['creds' => $encrypted, 'slug' => $slug]);
    }

    public function disconnect(string $slug): bool
    {
        $stmt = Database::connection()->prepare(
            "UPDATE integrations SET status = 'not_connected', credentials_encrypted = NULL, connected_by = NULL, connected_at = NULL, updated_at = NOW()
             WHERE slug = :slug"
        );

        return $stmt->execute(['slug' => $slug]);
    }

    public function markError(string $slug): void
    {
        Database::connection()->prepare("UPDATE integrations SET status = 'error', updated_at = NOW() WHERE slug = :slug")
            ->execute(['slug' => $slug]);
    }

    private function encryptionKey(): string
    {
        return hash('sha256', (string) config('app.key'), true);
    }

    private function encrypt(string $plaintext): string
    {
        $iv = random_bytes(openssl_cipher_iv_length(self::ENCRYPTION_CIPHER));
        $cipherText = openssl_encrypt($plaintext, self::ENCRYPTION_CIPHER, $this->encryptionKey(), OPENSSL_RAW_DATA, $iv);

        return base64_encode($iv . $cipherText);
    }

    private function decrypt(string $encoded): ?string
    {
        $raw = base64_decode($encoded, true);
        if ($raw === false) {
            return null;
        }

        $ivLength = openssl_cipher_iv_length(self::ENCRYPTION_CIPHER);
        $iv = substr($raw, 0, $ivLength);
        $cipherText = substr($raw, $ivLength);

        $plaintext = openssl_decrypt($cipherText, self::ENCRYPTION_CIPHER, $this->encryptionKey(), OPENSSL_RAW_DATA, $iv);

        return $plaintext === false ? null : $plaintext;
    }
}
