<?php

declare(strict_types=1);

final class MyronExtensionScopeGrantRepository
{
    public function __construct(private readonly PDO $pdo)
    {
    }

    public function schemaReady(): bool
    {
        return $this->tableExists('extension_scope_grants')
            && $this->tableExists('extension_load_errors');
    }

    /**
     * @return array{vendor:string,name:string,version:string,enabled:int,granted_scopes:string,created_at:string,updated_at:string}|null
     */
    public function findByVendor(string $vendor): ?array
    {
        if (!$this->schemaReady()) {
            return null;
        }

        $statement = $this->pdo->prepare('SELECT * FROM extension_scope_grants WHERE vendor = :vendor LIMIT 1');
        $statement->execute([':vendor' => $vendor]);
        $row = $statement->fetch();

        return is_array($row) ? $this->normalizeGrantRow($row) : null;
    }

    /**
     * @return list<array{vendor:string,name:string,version:string,enabled:int,granted_scopes:string,created_at:string,updated_at:string}>
     */
    public function all(): array
    {
        if (!$this->schemaReady()) {
            return [];
        }

        $statement = $this->pdo->query('SELECT * FROM extension_scope_grants ORDER BY vendor ASC');
        $rows = $statement ? $statement->fetchAll() : [];
        $out = [];
        foreach ($rows as $row) {
            if (is_array($row)) {
                $out[] = $this->normalizeGrantRow($row);
            }
        }
        return $out;
    }

    /**
     * @param list<string> $grantedScopes
     */
    public function register(string $vendor, string $name, string $version, array $grantedScopes): void
    {
        $this->assertVendor($vendor);
        $name = trim($name);
        $version = trim($version);
        if ($name === '') {
            throw new InvalidArgumentException('Extension name is required.');
        }
        if ($version === '') {
            throw new InvalidArgumentException('Extension version is required.');
        }
        $scopes = $this->normalizeScopeList($grantedScopes);
        $now = myronNow();

        $sql = 'INSERT INTO extension_scope_grants (vendor, name, version, enabled, granted_scopes, created_at, updated_at)
                VALUES (:vendor, :name, :version, 1, :granted_scopes, :created_at, :updated_at)
                ON CONFLICT(vendor) DO UPDATE SET
                    name = excluded.name,
                    version = excluded.version,
                    enabled = 1,
                    granted_scopes = excluded.granted_scopes,
                    updated_at = excluded.updated_at';

        if ($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql') {
            $sql = 'INSERT INTO extension_scope_grants (vendor, name, version, enabled, granted_scopes, created_at, updated_at)
                    VALUES (:vendor, :name, :version, 1, :granted_scopes, :created_at, :updated_at)
                    ON DUPLICATE KEY UPDATE
                        name = VALUES(name),
                        version = VALUES(version),
                        enabled = 1,
                        granted_scopes = VALUES(granted_scopes),
                        updated_at = VALUES(updated_at)';
        }

        $statement = $this->pdo->prepare($sql);
        $statement->execute([
            ':vendor' => $vendor,
            ':name' => $name,
            ':version' => $version,
            ':granted_scopes' => implode(',', $scopes),
            ':created_at' => $now,
            ':updated_at' => $now,
        ]);
    }

    public function setEnabled(string $vendor, bool $enabled): void
    {
        $statement = $this->pdo->prepare(
            'UPDATE extension_scope_grants SET enabled = :enabled, updated_at = :updated_at WHERE vendor = :vendor'
        );
        $statement->execute([
            ':enabled' => $enabled ? 1 : 0,
            ':updated_at' => myronNow(),
            ':vendor' => $vendor,
        ]);
    }

    public function revokeScope(string $vendor, string $scope): void
    {
        $row = $this->findByVendor($vendor);
        if ($row === null) {
            return;
        }
        $remaining = array_values(array_filter(
            MyronApiScopes::parseList($row['granted_scopes']),
            static fn (string $held): bool => $held !== $scope
        ));
        $statement = $this->pdo->prepare(
            'UPDATE extension_scope_grants SET granted_scopes = :granted_scopes, updated_at = :updated_at WHERE vendor = :vendor'
        );
        $statement->execute([
            ':granted_scopes' => implode(',', $remaining),
            ':updated_at' => myronNow(),
            ':vendor' => $vendor,
        ]);
    }

    public function recordLoadError(string $vendor, string $message): void
    {
        if (!$this->tableExists('extension_load_errors')) {
            return;
        }
        $statement = $this->pdo->prepare(
            'INSERT INTO extension_load_errors (vendor, error_message, occurred_at) VALUES (:vendor, :error_message, :occurred_at)'
        );
        $statement->execute([
            ':vendor' => $vendor,
            ':error_message' => substr($message, 0, 2000),
            ':occurred_at' => myronNow(),
        ]);
    }

    public function clearLoadErrors(string $vendor): void
    {
        if (!$this->tableExists('extension_load_errors')) {
            return;
        }
        $statement = $this->pdo->prepare('DELETE FROM extension_load_errors WHERE vendor = :vendor');
        $statement->execute([':vendor' => $vendor]);
    }

    /**
     * @return array<string, array{vendor:string,error_message:string,occurred_at:string}>
     */
    public function latestLoadErrorsByVendor(): array
    {
        if (!$this->tableExists('extension_load_errors')) {
            return [];
        }

        $statement = $this->pdo->query(
            'SELECT vendor, error_message, occurred_at
             FROM extension_load_errors
             ORDER BY occurred_at DESC'
        );
        $rows = $statement ? $statement->fetchAll() : [];
        $latest = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $vendor = (string) ($row['vendor'] ?? '');
            if ($vendor === '' || isset($latest[$vendor])) {
                continue;
            }
            $latest[$vendor] = [
                'vendor' => $vendor,
                'error_message' => (string) ($row['error_message'] ?? ''),
                'occurred_at' => (string) ($row['occurred_at'] ?? ''),
            ];
        }
        return $latest;
    }

    public function tableExists(string $table): bool
    {
        if (!preg_match('/^[A-Za-z0-9_]+$/', $table)) {
            return false;
        }

        if ($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite') {
            $statement = $this->pdo->prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = :table");
            $statement->execute([':table' => $table]);
            return $statement->fetchColumn() !== false;
        }

        $statement = $this->pdo->prepare('SHOW TABLES LIKE :table');
        $statement->execute([':table' => $table]);
        return $statement->fetchColumn() !== false;
    }

    /**
     * @param array<string, mixed> $row
     * @return array{vendor:string,name:string,version:string,enabled:int,granted_scopes:string,created_at:string,updated_at:string}
     */
    private function normalizeGrantRow(array $row): array
    {
        return [
            'vendor' => (string) ($row['vendor'] ?? ''),
            'name' => (string) ($row['name'] ?? ''),
            'version' => (string) ($row['version'] ?? ''),
            'enabled' => (int) ($row['enabled'] ?? 0),
            'granted_scopes' => (string) ($row['granted_scopes'] ?? ''),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'updated_at' => (string) ($row['updated_at'] ?? ''),
        ];
    }

    /**
     * @param list<string> $scopes
     * @return list<string>
     */
    private function normalizeScopeList(array $scopes): array
    {
        $allowed = array_fill_keys(MyronApiScopes::ALL_BASE, true);
        $normalized = [];
        foreach ($scopes as $scope) {
            $scope = trim((string) $scope);
            if ($scope === '') {
                continue;
            }
            if (!isset($allowed[$scope])) {
                throw new InvalidArgumentException('Unknown extension scope: ' . $scope . '.');
            }
            $normalized[] = $scope;
        }
        return array_values(array_unique($normalized));
    }

    private function assertVendor(string $vendor): void
    {
        if (!preg_match('/^[a-z][a-z0-9_]*$/', $vendor)) {
            throw new InvalidArgumentException('Vendor must be snake_case.');
        }
    }
}
