<?php

namespace Tests\E2E\Services\Databases\VectorsDB;

use PHPUnit\Framework\Attributes\Depends;
use Tests\E2E\Client;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;

trait DatabasesBase
{
    public function testCreateDatabase(): array
    {
        /**
         * Test for SUCCESS
         */
        $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'databaseId' => ID::unique(),
            'name' => 'Test Database'
        ]);

        $this->assertNotEmpty($database['body']['$id']);
        $this->assertEquals(201, $database['headers']['status-code']);
        $this->assertEquals('Test Database', $database['body']['name']);
        $this->assertEquals('vectorsdb', $database['body']['type']);

        return ['databaseId' => $database['body']['$id']];
    }

    #[Depends('testCreateCollectionSample')]
    public function testCreateDocument(array $data): array
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];

        // Build embedding vector matching collection dimensions (1536)
        $vector = array_fill(0, 1536, 0.1);
        $vector[0] = 1.0;

        $res = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'documentId' => ID::unique(),
            'data' => [
                'embeddings' => $vector,
                'metadata' => ['type' => 'sample', 'rank' => 1]
            ],
            'permissions' => [
                Permission::read(Role::any()),
                Permission::update(Role::any()),
                Permission::delete(Role::any()),
            ]
        ]);

        $this->assertEquals(201, $res['headers']['status-code']);
        $this->assertNotEmpty($res['body']['$id']);
        $documentId = $res['body']['$id'];

        // createdAt/updatedAt should be present and equal on initial create
        $this->assertArrayHasKey('$createdAt', $res['body']);
        $this->assertArrayHasKey('$updatedAt', $res['body']);
        $this->assertNotEmpty($res['body']['$createdAt']);
        $this->assertNotEmpty($res['body']['$updatedAt']);
        $this->assertEquals($res['body']['$createdAt'], $res['body']['$updatedAt']);

        // Edge: invalid dimensions (vector too short) → expect 4xx
        $badVec = [1.0, 0.0];
        $bad = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'documentId' => ID::unique(),
            'data' => [
                'embeddings' => $badVec,
                'metadata' => ['type' => 'bad']
            ],
        ]);
        $this->assertGreaterThanOrEqual(400, $bad['headers']['status-code']);
        $this->assertLessThan(500, $bad['headers']['status-code']);

        // Edge: invalid type values (strings) → expect 4xx
        $strVec = ['1.0', '0.0', '0.0'];
        $bad2 = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'documentId' => ID::unique(),
            'data' => [
                'embeddings' => $strVec,
                'metadata' => ['type' => 'bad-strings']
            ],
        ]);
        $this->assertGreaterThanOrEqual(400, $bad2['headers']['status-code']);
        $this->assertLessThan(500, $bad2['headers']['status-code']);

        // Create another valid doc to verify list totals later
        $res2 = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'documentId' => ID::unique(),
            'data' => [
                'embeddings' => $vector,
                'metadata' => ['type' => 'sample', 'rank' => 99]
            ],
            'permissions' => [Permission::read(Role::any())]
        ]);
        $this->assertEquals(201, $res2['headers']['status-code']);
        $documentId2 = $res2['body']['$id'];

        return [
            'databaseId' => $databaseId,
            'collectionId' => $collectionId,
            'documentId' => $documentId,
            'documentId2' => $documentId2,
            'createdAt' => $res['body']['$createdAt'],
            'updatedAt' => $res['body']['$updatedAt'],
        ];
    }

    #[Depends('testCreateDocument')]
    public function testGetDocument(array $data): array
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];
        $documentId = $data['documentId'];

        $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);

        $this->assertEquals(200, $res['headers']['status-code']);
        $this->assertEquals($documentId, $res['body']['$id']);

        // Edge: missing document should return 404
        $missing = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);
        $this->assertEquals(404, $missing['headers']['status-code']);

        return $data;
    }

    #[Depends('testCreateDocument')]
    public function testListDocuments(array $data): array
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];

        $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [Query::limit(5)->toString()]
        ]);

        $this->assertEquals(200, $list['headers']['status-code']);
        $this->assertIsInt($list['body']['total']);
        $this->assertGreaterThanOrEqual(1, $list['body']['total']);

        // Pagination: limit 1, then offset 1
        $page1 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::limit(1)->toString(),
                Query::orderAsc('$id')->toString()
            ]
        ]);
        $this->assertEquals(200, $page1['headers']['status-code']);
        $this->assertEquals(1, \count($page1['body']['documents'] ?? []));

        $page2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::limit(1)->toString(),
                Query::offset(1)->toString(),
                Query::orderAsc('$id')->toString()
            ]
        ]);
        $this->assertEquals(200, $page2['headers']['status-code']);
        $this->assertEquals(1, \count($page2['body']['documents'] ?? []));

        return $data;
    }

    #[Depends('testCreateDocument')]
    public function testUpsertDocument(array $data): array
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];
        $documentId = $data['documentId'];

        $vector = array_fill(0, 1536, 0.0);
        // $vector[1] = 1.0;

        $upd = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'data' => [
                'embeddings' => $vector,
                'metadata' => ['type' => 'sample', 'rank' => 2]
            ]
        ]);

        $this->assertEquals(200, $upd['headers']['status-code']);

        // Verify update took effect
        $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);
        $this->assertEquals(200, $get['headers']['status-code']);
        $this->assertEquals(2, $get['body']['metadata']['rank']);
        // updatedAt should be greater or changed from earlier
        $this->assertArrayHasKey('$updatedAt', $get['body']);

        return $data;
    }

    #[Depends('testUpsertDocument')]
    public function testUpdateDocument(array $data): array
    {
        // Upsert is used for update semantics
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];
        $documentId = $data['documentId'];

        $vector = array_fill(0, 1536, 0.0);
        $vector[2] = 1.0;

        $upd = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'data' => [
                'embeddings' => $vector,
                'metadata' => ['type' => 'sample', 'rank' => 3]
            ]
        ]);

        $this->assertEquals(200, $upd['headers']['status-code']);

        // Re-update to check idempotence and metadata replacement
        $vector2 = array_fill(0, 1536, 0.0);
        $vector2[3] = 1.0;
        $upd2 = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'data' => [
                'embeddings' => $vector2,
                'metadata' => ['type' => 'sample', 'rank' => 4]
            ]
        ]);
        $this->assertEquals(200, $upd2['headers']['status-code']);

        // Verify updatedAt changed again
        $get2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);
        $this->assertEquals(200, $get2['headers']['status-code']);
        $this->assertArrayHasKey('$updatedAt', $get2['body']);

        return $data;
    }

    #[Depends('testUpdateDocument')]
    public function testDocumentsVectorQueries(array $data): array
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];

        // Create two more documents with distinct embeddings
        $mk = function (array $vec, string $name) use ($databaseId, $collectionId) {
            $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'x-appwrite-key' => $this->getProject()['apiKey']
            ], [
                'documentId' => ID::unique(),
                'data' => [
                    'embeddings' => $vec,
                    'metadata' => ['name' => $name]
                ],
                'permissions' => [Permission::read(Role::any())]
            ]);
        };

        $vA = array_fill(0, 1536, 0.0);
        $vA[0] = 1.0; // close to [1,0,0,...]
        $vB = array_fill(0, 1536, 0.0);
        $vB[1] = 1.0; // close to [0,1,0,...]

        $mk($vA, 'A');
        $mk($vB, 'B');

        // Dot product
        $dot = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::vectorDot('embeddings', $vA)->toString(),
                Query::limit(2)->toString()
            ]
        ]);
        $this->assertEquals(200, $dot['headers']['status-code']);
        $this->assertGreaterThanOrEqual(1, $dot['body']['total']);

        // Cosine
        $cos = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::vectorCosine('embeddings', $vB)->toString(),
                Query::limit(2)->toString()
            ]
        ]);
        $this->assertEquals(200, $cos['headers']['status-code']);
        $this->assertGreaterThanOrEqual(1, $cos['body']['total']);

        // Euclidean
        $eu = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::vectorEuclidean('embeddings', $vA)->toString(),
                Query::limit(2)->toString()
            ]
        ]);
        $this->assertEquals(200, $eu['headers']['status-code']);
        $this->assertGreaterThanOrEqual(1, $eu['body']['total']);

        // Combined vector + metadata filters
        $combo = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::vectorCosine('embeddings', $vA)->toString(),
                Query::notEqual('metadata', [['name' => 'B']])->toString(),
                Query::limit(2)->toString()
            ]
        ]);
        $this->assertEquals(200, $combo['headers']['status-code']);
        $this->assertGreaterThanOrEqual(1, $combo['body']['total']);

        // Ordering with $id ascending combined with vector
        $ordered = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::vectorDot('embeddings', $vA)->toString(),
                Query::orderAsc('$id')->toString(),
                Query::limit(3)->toString()
            ]
        ]);
        $this->assertEquals(200, $ordered['headers']['status-code']);

        return $data;
    }

    #[Depends('testDocumentsVectorQueries')]
    public function testDeleteDocument(array $data): void
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];
        $documentId = $data['documentId'];

        $del = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);
        $this->assertEquals(204, $del['headers']['status-code']);

        // GET after delete should be 404
        $getMissing = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);
        $this->assertEquals(404, $getMissing['headers']['status-code']);

        // List should still work and reflect at least one less document compared to earlier pages (best-effort)
        $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [Query::limit(5)->toString()]
        ]);
        $this->assertEquals(200, $list['headers']['status-code']);
    }

    #[Depends('testCreateCollectionSample')]
    public function testDocumentPermissions(array $data): void
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];

        // Create doc readable only by a specific user
        $docId = ID::unique();
        $vector = array_fill(0, 1536, 0.0);
        $vector[0] = 1.0;
        $create = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'documentId' => $docId,
            'data' => [
                'embeddings' => $vector,
                'metadata' => ['scope' => 'private']
            ],
            'permissions' => [
                Permission::read(Role::user($this->getUser()['$id']))
            ]
        ]);
        $this->assertEquals(201, $create['headers']['status-code']);

        $guest = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$docId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id']
        ]);
        $this->assertEquals(404, $guest['headers']['status-code']);

        // GET with key should succeed regardless of document user-level permission
        $withKey = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$docId}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);
        $this->assertEquals(200, $withKey['headers']['status-code']);
    }

    #[Depends('testCreateDatabase')]
    public function testCreateCollection(array $data): array
    {
        $databaseId = $data['databaseId'];
        /**
         * Test for SUCCESS
         */
        $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]), [
            'collectionId' => ID::unique(),
            'name' => 'Movies',
            'documentSecurity' => true,
            'dimension' => 1536,
            'permissions' => [
                Permission::create(Role::user($this->getUser()['$id'])),
            ],
        ]);

        $this->assertEquals(201, $movies['headers']['status-code']);
        $this->assertEquals($movies['body']['name'], 'Movies');

        $actors = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]), [
            'collectionId' => ID::unique(),
            'name' => 'Actors',
            'documentSecurity' => true,
            'dimension' => 1536,
            'permissions' => [
                Permission::create(Role::user($this->getUser()['$id'])),
            ],
        ]);

        $this->assertEquals(201, $actors['headers']['status-code']);
        $this->assertEquals($actors['body']['name'], 'Actors');

        return [
            'databaseId' => $databaseId,
            'moviesId' => $movies['body']['$id'],
            'actorsId' => $actors['body']['$id'],
        ];
    }

    public function testCreateDatabaseSample(): array
    {
        $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'databaseId' => ID::unique(),
            'name' => 'Sample VectorsDB'
        ]);

        $this->assertNotEmpty($database['body']['$id']);
        $this->assertEquals(201, $database['headers']['status-code']);
        $this->assertEquals('Sample VectorsDB', $database['body']['name']);
        $this->assertEquals('vectorsdb', $database['body']['type']);

        return ['databaseId' => $database['body']['$id']];
    }

    #[Depends('testCreateDatabaseSample')]
    public function testCreateCollectionSample(array $data): array
    {
        $databaseId = $data['databaseId'];

        $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]), [
            'collectionId' => ID::unique(),
            'name' => 'Sample Collection',
            'dimension' => 1536,
            'documentSecurity' => true,
            'permissions' => [
                Permission::create(Role::user($this->getUser()['$id'])),
            ],
        ]);

        $this->assertEquals(201, $collection['headers']['status-code']);
        $this->assertEquals('Sample Collection', $collection['body']['name']);
        $this->assertEquals(1536, $collection['body']['dimension']);

        return [
            'databaseId' => $databaseId,
            'collectionId' => $collection['body']['$id'],
        ];
    }

    public function testCreateMultipleDatabasesWithCollections(): array
    {
        $projectId = $this->getProject()['$id'];
        $apiKey = $this->getProject()['apiKey'];
        $userId = $this->getUser()['$id'];

        /**
         * Helper to create a database
         */
        $createDatabase = function (string $name) use ($projectId, $apiKey) {
            $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
                'content-type' => 'application/json',
                'x-appwrite-project' => $projectId,
                'x-appwrite-key' => $apiKey
            ], [
                'databaseId' => ID::unique(),
                'name' => $name
            ]);

            $this->assertEquals(201, $db['headers']['status-code']);
            $this->assertEquals('vectorsdb', $db['body']['type']);
            $this->assertEquals($name, $db['body']['name']);
            $this->assertNotEmpty($db['body']['$id']);

            return $db['body']['$id'];
        };

        /**
         * Helper to create a collection
         */
        $createCollection = function (string $databaseId, string $name, int $dimensions = 1536) use ($projectId, $apiKey, $userId) {
            $res = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
                'content-type' => 'application/json',
                'x-appwrite-project' => $projectId,
                'x-appwrite-key' => $apiKey
            ], [
                'collectionId' => ID::unique(),
                'name' => $name,
                'documentSecurity' => true,
                'dimension' => $dimensions,
                'permissions' => [
                    Permission::create(Role::user($userId)),
                ],
            ]);

            $this->assertEquals(201, $res['headers']['status-code']);
            $this->assertEquals($name, $res['body']['name']);

            return $res['body']['$id'];
        };

        /**
         * === Database 1: MediaDB ===
         */
        $mediaDbId = $createDatabase('MediaDB');

        $mediaCollections = ['Movies', 'Actors', 'Directors'];
        $mediaCollectionIds = [];

        foreach ($mediaCollections as $col) {
            $mediaCollectionIds[$col] = $createCollection($mediaDbId, $col);
        }

        /**
         * === Database 2: ContentDB ===
         */
        $contentDbId = $createDatabase('ContentDB');

        $contentCollections = ['Articles', 'Authors'];
        $contentCollectionIds = [];

        foreach ($contentCollections as $col) {
            $contentCollectionIds[$col] = $createCollection($contentDbId, $col);
        }

        // Create a tiny-dimension collection and insert a document to validate vector and object attributes
        $tinyCollectionName = 'VectorsTiny';
        $tinyDimensions = 8;
        $tinyCollectionId = $createCollection($mediaDbId, $tinyCollectionName, $tinyDimensions);

        return [
            'databases' => [
                'MediaDB' => [
                    'id' => $mediaDbId,
                    'collections' => $mediaCollectionIds + ['VectorsTiny' => $tinyCollectionId],
                ],
                'ContentDB' => [
                    'id' => $contentDbId,
                    'collections' => $contentCollectionIds,
                ],
            ]
        ];
    }

    public function testInvalidCollectionDimensions(): void
    {
        // dimensions = 0 -> expect 4xx
        $bad0 = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'databaseId' => ID::unique(),
            'name' => 'BadDims0'
        ]);
        $this->assertEquals(201, $bad0['headers']['status-code']);
        $dbId = $bad0['body']['$id'];
        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $dbId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'collectionId' => ID::unique(),
            'name' => 'ZeroDims',
            'documentSecurity' => true,
            'dimension' => 0,
            'permissions' => [Permission::create(Role::user($this->getUser()['$id']))],
        ]);
        $this->assertGreaterThanOrEqual(400, $col['headers']['status-code']);
        $this->assertLessThan(500, $col['headers']['status-code']);

        // dimensions too large -> expect 4xx
        $col2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $dbId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'collectionId' => ID::unique(),
            'name' => 'HugeDims',
            'documentSecurity' => true,
            'dimension' => 16001,
            'permissions' => [Permission::create(Role::user($this->getUser()['$id']))],
        ]);
        $this->assertGreaterThanOrEqual(400, $col2['headers']['status-code']);
        $this->assertLessThan(500, $col2['headers']['status-code']);
    }

    public function testSingleDimensionVectorCollection(): void
    {
        $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'databaseId' => ID::unique(),
            'name' => 'SingleDim'
        ]);
        $this->assertEquals(201, $db['headers']['status-code']);
        $databaseId = $db['body']['$id'];

        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'collectionId' => ID::unique(),
            'name' => 'OneDim',
            'documentSecurity' => true,
            'dimension' => 1,
            'permissions' => [Permission::create(Role::user($this->getUser()['$id']))],
        ]);
        $this->assertEquals(201, $col['headers']['status-code']);
        $collectionId = $col['body']['$id'];

        // Create two docs with 1D embeddings
        $id1 = ID::unique();
        $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id1}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'data' => ['embeddings' => [1.0]]
        ]);
        $id2 = ID::unique();
        $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id2}", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'data' => ['embeddings' => [0.5]]
        ]);

        // Query with vectorCosine
        $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [Query::vectorCosine('embeddings', [1.0])->toString(), Query::limit(2)->toString()]
        ]);
        $this->assertEquals(200, $res['headers']['status-code']);
        $this->assertGreaterThanOrEqual(1, $res['body']['total']);
    }

    public function testVectorInvalidValues(): void
    {
        $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'databaseId' => ID::unique(),
            'name' => 'InvalidVals'
        ]);
        $this->assertEquals(201, $db['headers']['status-code']);
        $databaseId = $db['body']['$id'];

        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'collectionId' => ID::unique(),
            'name' => 'Docs',
            'documentSecurity' => true,
            'dimension' => 3,
            'permissions' => [Permission::create(Role::user($this->getUser()['$id']))],
        ]);
        $this->assertEquals(201, $col['headers']['status-code']);
        $collectionId = $col['body']['$id'];

        $badPayloads = [
            ['embeddings' => [INF, 0.0, 0.0]],
            ['embeddings' => [-INF, 0.0, 0.0]],
            ['embeddings' => [NAN, 0.0, 0.0]],
            ['embeddings' => ['x' => 1.0, 'y' => 0.0, 'z' => 0.0]],
            ['embeddings' => [1.0, null, 0.0]],
            ['embeddings' => [[1.0], [0.0], [0.0]]],
            ['embeddings' => [true, false, true]],
            ['embeddings' => [1.0, '2.0', 3.0]],
            (function () {
                $v = [];
                $v[0] = 1.0;
                $v[2] = 1.0;
                return ['embeddings' => $v];
            })(),
        ];

        foreach ($badPayloads as $payload) {
            $resp = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'x-appwrite-key' => $this->getProject()['apiKey']
            ], [
                'data' => $payload
            ]);
            $this->assertGreaterThanOrEqual(400, $resp['headers']['status-code']);
            $this->assertLessThan(500, $resp['headers']['status-code']);
        }
    }

    public function testVectorAllZerosAndQuery(): void
    {
        $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'databaseId' => ID::unique(),
            'name' => 'ZerosDB'
        ]);
        $this->assertEquals(201, $db['headers']['status-code']);
        $databaseId = $db['body']['$id'];

        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'collectionId' => ID::unique(),
            'name' => 'Zeros',
            'documentSecurity' => true,
            'dimension' => 3,
            'permissions' => [Permission::create(Role::user($this->getUser()['$id']))],
        ]);
        $this->assertEquals(201, $col['headers']['status-code']);
        $collectionId = $col['body']['$id'];

        $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'data' => ['embeddings' => [0.0, 0.0, 0.0]] ]);

        $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'data' => ['embeddings' => [1.0, 0.0, 0.0]] ]);

        $results = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'queries' => [Query::vectorCosine('embeddings', [1.0, 0.0, 0.0])->toString()] ]);
        $this->assertEquals(200, $results['headers']['status-code']);
        $this->assertGreaterThan(0, $results['body']['total']);
    }

    public function testVectorMultipleQueriesRejection(): void
    {
        // Create a simple DB and collection
        $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'databaseId' => ID::unique(), 'name' => 'MultiQueryDB' ]);
        $this->assertEquals(201, $db['headers']['status-code']);
        $databaseId = $db['body']['$id'];
        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'collectionId' => ID::unique(), 'name' => 'Docs', 'documentSecurity' => true, 'dimension' => 3, 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))] ]);
        $this->assertEquals(201, $col['headers']['status-code']);
        $collectionId = $col['body']['$id'];

        // Two vector queries simultaneously should fail
        $fail = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'queries' => [
                Query::vectorCosine('embeddings', [1.0, 0.0, 0.0])->toString(),
                Query::vectorEuclidean('embeddings', [1.0, 0.0, 0.0])->toString()
            ]
        ]);
        $this->assertGreaterThanOrEqual(400, $fail['headers']['status-code']);
        $this->assertLessThan(500, $fail['headers']['status-code']);
    }

    public function testVectorQueryOnNonVectorAttribute(): void
    {
        $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'databaseId' => ID::unique(), 'name' => 'NonVec' ]);
        $this->assertEquals(201, $db['headers']['status-code']);
        $databaseId = $db['body']['$id'];
        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'collectionId' => ID::unique(), 'name' => 'Docs', 'documentSecurity' => true, 'dimension' => 3, 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))] ]);
        $this->assertEquals(201, $col['headers']['status-code']);
        $collectionId = $col['body']['$id'];

        // Query on non-vector attribute 'metadata' should fail
        $fail = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'queries' => [Query::vectorCosine('metadata', [1.0, 0.0, 0.0])->toString()] ]);
        $this->assertGreaterThanOrEqual(400, $fail['headers']['status-code']);
        $this->assertLessThan(500, $fail['headers']['status-code']);
    }

    public function testVectorEmptyQueryCollection(): void
    {
        $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'databaseId' => ID::unique(), 'name' => 'EmptyQ' ]);
        $this->assertEquals(201, $db['headers']['status-code']);
        $databaseId = $db['body']['$id'];
        $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'collectionId' => ID::unique(), 'name' => 'Docs', 'documentSecurity' => true, 'dimension' => 3, 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))] ]);
        $this->assertEquals(201, $col['headers']['status-code']);
        $collectionId = $col['body']['$id'];

        $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [ 'queries' => [Query::vectorCosine('embeddings', [1.0, 0.0, 0.0])->toString()] ]);
        $this->assertEquals(200, $res['headers']['status-code']);
        $this->assertEquals(0, $res['body']['total']);
    }

    #[Depends('testCreateCollection')]
    public function testCreateIndexes(array $data): array
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['moviesId'];

        // HNSW Euclidean
        $idxEuclidean = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'key' => 'embedding_euclidean',
            'type' => Database::INDEX_HNSW_EUCLIDEAN,
            'attributes' => ['embeddings']
        ]);
        $this->assertEquals(202, $idxEuclidean['headers']['status-code']);

        // HNSW Dot (Inner Product)
        $idxDot = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'key' => 'embedding_dot',
            'type' => Database::INDEX_HNSW_DOT,
            'attributes' => ['embeddings']
        ]);
        $this->assertEquals(202, $idxDot['headers']['status-code']);

        // HNSW Cosine
        $idxCosine = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ], [
            'key' => 'embedding_cosine',
            'type' => Database::INDEX_HNSW_COSINE,
            'attributes' => ['embeddings']
        ]);
        $this->assertEquals(202, $idxCosine['headers']['status-code']);

        return [
            'databaseId' => $databaseId,
            'collectionId' => $collectionId,
            'indexes' => ['embedding_euclidean', 'embedding_dot', 'embedding_cosine']
        ];
    }

    #[Depends('testCreateIndexes')]
    public function testListIndexes(array $data): void
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];

        $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey']
        ]);

        $this->assertEquals(200, $list['headers']['status-code']);
        $keys = array_map(fn ($i) => $i['key'], $list['body']['indexes'] ?? []);
        foreach ($data['indexes'] as $expectedKey) {
            $this->assertContains($expectedKey, $keys);
        }
    }

    #[Depends('testCreateIndexes')]
    public function testGetIndexByKey(array $data): void
    {
        $databaseId = $data['databaseId'];
        $collectionId = $data['collectionId'];

        $keysToTypes = [
            'embedding_euclidean' => Database::INDEX_HNSW_EUCLIDEAN,
            'embedding_dot' => Database::INDEX_HNSW_DOT,
            'embedding_cosine' => Database::INDEX_HNSW_COSINE,
        ];

        foreach ($keysToTypes as $key => $type) {
            $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/{$key}", [
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'x-appwrite-key' => $this->getProject()['apiKey']
            ]);
            $this->assertEquals(200, $res['headers']['status-code']);
            $this->assertEquals($key, $res['body']['key']);
            $this->assertEquals($type, $res['body']['type']);
        }
    }

}
