<?php

namespace Tests\E2E\Services\Databases\Permissions;

use PHPUnit\Framework\Attributes\DataProvider;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ApiLegacy;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\SchemaPolling;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;

class LegacyPermissionsTeamTest extends Scope
{
    use DatabasesPermissionsBase;
    use ProjectCustom;
    use SideClient;
    use ApiLegacy;
    use SchemaPolling;

    public array $collections = [];
    public string $databaseId = 'testpermdb_legacy';

    public function createTeams(): array
    {
        return [
            'team1' => $this->createTeam('team1', 'Team 1'),
            'team2' => $this->createTeam('team2', 'Team 2'),
        ];
    }

    public function createUsers(): array
    {
        return [
            'user1' => $this->createUser('user1', 'lorem@ipsum.com'),
            'user2' => $this->createUser('user2', 'dolor@ipsum.com'),
            'user3' => $this->createUser('user3', 'sit@ipsum.com'),
        ];
    }

    public function createCollections($teams)
    {
        $db = $this->client->call(
            Client::METHOD_POST,
            $this->getDatabaseUrl(),
            $this->getServerHeader(),
            [
                'databaseId' => $this->databaseId,
                'name' => 'Test Database',
            ]
        );
        $this->assertEquals(201, $db['headers']['status-code']);

        $collection1 = $this->client->call(
            Client::METHOD_POST,
            $this->getContainerUrl($this->databaseId),
            $this->getServerHeader(),
            [
                $this->getContainerIdParam() => ID::custom('collection1'),
                'name' => 'Collection 1',
                'permissions' => [
                    Permission::read(Role::team($teams['team1']['$id'])),
                    Permission::create(Role::team($teams['team1']['$id'], 'admin')),
                    Permission::update(Role::team($teams['team1']['$id'], 'admin')),
                    Permission::delete(Role::team($teams['team1']['$id'], 'admin')),
                ],
            ]
        );

        $this->collections['collection1'] = $collection1['body']['$id'];

        $this->client->call(
            Client::METHOD_POST,
            $this->getSchemaUrl($this->databaseId, $this->collections['collection1'], 'string'),
            $this->getServerHeader(),
            [
                'key' => 'title',
                'size' => 256,
                'required' => true,
            ]
        );

        $collection2 = $this->client->call(
            Client::METHOD_POST,
            $this->getContainerUrl($this->databaseId),
            $this->getServerHeader(),
            [
                $this->getContainerIdParam() => ID::custom('collection2'),
                'name' => 'Collection 2',
                'permissions' => [
                    Permission::read(Role::team($teams['team2']['$id'])),
                    Permission::create(Role::team($teams['team2']['$id'], 'owner')),
                    Permission::update(Role::team($teams['team2']['$id'], 'owner')),
                    Permission::delete(Role::team($teams['team2']['$id'], 'owner')),
                ]
            ]
        );

        $this->collections['collection2'] = $collection2['body']['$id'];

        $this->client->call(
            Client::METHOD_POST,
            $this->getSchemaUrl($this->databaseId, $this->collections['collection2'], 'string'),
            $this->getServerHeader(),
            [
                'key' => 'title',
                'size' => 256,
                'required' => true,
            ]
        );

        $this->waitForAttribute($this->databaseId, $this->collections['collection1'], 'title');
        $this->waitForAttribute($this->databaseId, $this->collections['collection2'], 'title');

        return $this->collections;
    }

    /*
     * $success = can $user read from $collection
     * [$user, $collection, $success]
     */
    public static function readDocumentsProvider(): array
    {
        return [
            ['user1', 'collection1', true],
            ['user2', 'collection1', false],
            ['user3', 'collection1', true],
            ['user1', 'collection2', false],
            ['user2', 'collection2', true],
            ['user3', 'collection2', true],
        ];
    }

    /*
     * $success = can $user write to $collection
     * [$user, $collection, $success]
     */
    public static function writeDocumentsProvider(): array
    {
        return [
            ['user1', 'collection1', true],
            ['user2', 'collection1', false],
            ['user3', 'collection1', false],
            ['user1', 'collection2', false],
            ['user2', 'collection2', true],
            ['user3', 'collection2', false],
        ];
    }

    /**
     * Setup database helper with caching
     */
    protected function setupDatabase(): array
    {
        $cacheKey = $this->getProject()['$id'] . '_' . static::class;

        if (!empty(self::$setupDatabaseCache[$cacheKey])) {
            return self::$setupDatabaseCache[$cacheKey];
        }

        $this->createUsers();
        $this->createTeams();

        $this->addToTeam('user1', 'team1', ['admin']);
        $this->addToTeam('user2', 'team2', ['owner']);

        // user3 in both teams but with no roles
        $this->addToTeam('user3', 'team1');
        $this->addToTeam('user3', 'team2');

        $this->createCollections($this->teams);

        $response = $this->client->call(
            Client::METHOD_POST,
            $this->getRecordUrl($this->databaseId, $this->collections['collection1']),
            $this->getServerHeader(),
            [
                $this->getRecordIdParam() => ID::unique(),
                'data' => [
                    'title' => 'Lorem',
                ],
            ]
        );
        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(
            Client::METHOD_POST,
            $this->getRecordUrl($this->databaseId, $this->collections['collection2']),
            $this->getServerHeader(),
            [
                $this->getRecordIdParam() => ID::unique(),
                'data' => [
                    'title' => 'Ipsum',
                ],
            ]
        );
        $this->assertEquals(201, $response['headers']['status-code']);

        self::$setupDatabaseCache[$cacheKey] = $this->users;

        return self::$setupDatabaseCache[$cacheKey];
    }

    /**
     * Setup database test
     */
    public function testSetupDatabase(): void
    {
        $users = $this->setupDatabase();
        $this->assertNotEmpty($users);
    }

    #[DataProvider('readDocumentsProvider')]
    public function testReadDocuments($user, $collection, $success)
    {
        $users = $this->setupDatabase();

        $documents = $this->client->call(
            Client::METHOD_GET,
            $this->getRecordUrl($this->databaseId, $collection),
            [
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'],
            ]
        );

        if ($success) {
            $this->assertCount(1, $documents['body'][$this->getRecordResource()]);
        } else {
            $this->assertEquals(401, $documents['headers']['status-code']);
        }
    }

    #[DataProvider('writeDocumentsProvider')]
    public function testWriteDocuments($user, $collection, $success)
    {
        $users = $this->setupDatabase();

        $documents = $this->client->call(
            Client::METHOD_POST,
            $this->getRecordUrl($this->databaseId, $collection),
            [
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'],
            ],
            [
                $this->getRecordIdParam() => ID::unique(),
                'data' => [
                    'title' => 'Ipsum',
                ],
            ]
        );

        if ($success) {
            $this->assertEquals(201, $documents['headers']['status-code']);
        } else {
            // 401 if user is a part of team, 404 otherwise
            $this->assertContains($documents['headers']['status-code'], [401, 404]);
        }
    }
}
