<?php

namespace App\Tests\Service;

use App\Service\KeycloakAdminService;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class KeycloakAdminServiceTest extends TestCase
{
    private HttpClientInterface $httpClient;
    private KeycloakAdminService $service;

    protected function setUp(): void
    {
        $this->httpClient = $this->createStub(HttpClientInterface::class);
        $this->service = new KeycloakAdminService(
            $this->httpClient,
            'http://keycloak',
            'realm',
            'client_id',
            'client_secret'
        );
    }

    private function setupMocks(string $method, string $url, int $statusCode, array $data = [], array $headers = []): ResponseInterface
    {
        $response = $this->createStub(ResponseInterface::class);
        $response->method('getStatusCode')->willReturn($statusCode);
        $response->method('toArray')->willReturn($data);
        $response->method('getHeaders')->willReturn($headers);

        return $response;
    }

    private function setupCallback(array $responses): void
    {
        $this->httpClient->method('request')->willReturnCallback(function($method, $url) use ($responses) {
            if ($method === 'POST' && str_contains($url, '/token')) {
                $tokenResponse = $this->createStub(ResponseInterface::class);
                $tokenResponse->method('toArray')->willReturn(['access_token' => 't', 'expires_in' => 300]);
                return $tokenResponse;
            }
            foreach ($responses as $resp) {
                if ($method === $resp['method'] && str_contains($url, $resp['url'])) {
                    return $resp['response'];
                }
            }
            return $this->createStub(ResponseInterface::class);
        });
    }

    public function testCreateUser(): void
    {
        $resp = $this->setupMocks('POST', '/users', 201, [], ['location' => ['http://k/u/id']]);
        $this->setupCallback([['method' => 'POST', 'url' => '/users', 'response' => $resp]]);
        $result = $this->service->createUser('t@e.com', 'J', 'D');
        $this->assertTrue($result['created']);
        $this->assertEquals('id', $result['keycloakId']);
    }

    public function testCreateUserFailure(): void
    {
        $resp = $this->setupMocks('POST', '/users', 400);
        $this->setupCallback([['method' => 'POST', 'url' => '/users', 'response' => $resp]]);
        $result = $this->service->createUser('t@e.com', 'J', 'D');
        $this->assertFalse($result['created']);
    }

    public function testCreateUserWithoutLocation(): void
    {
        $resp = $this->setupMocks('POST', '/users', 201, [], []);
        $getUserIdResp = $this->setupMocks('GET', '/users', 200, [['id' => 'found_id']]);
        $this->setupCallback([
            ['method' => 'POST', 'url' => '/users', 'response' => $resp],
            ['method' => 'GET', 'url' => '/users', 'response' => $getUserIdResp],
        ]);
        $result = $this->service->createUser('t@e.com', 'J', 'D');
        $this->assertTrue($result['created']);
        $this->assertEquals('found_id', $result['keycloakId']);
    }

    public function testAddUserToGroup(): void
    {
        $groupResp = $this->setupMocks('GET', '/groups', 200, [['name' => 'G', 'id' => 'gid']]);
        $putResp = $this->setupMocks('PUT', '/groups/gid', 204);
        $this->setupCallback([
            ['method' => 'GET', 'url' => '/groups', 'response' => $groupResp],
            ['method' => 'PUT', 'url' => '/groups/gid', 'response' => $putResp]
        ]);
        $this->assertTrue($this->service->addUserToGroup('uid', 'G'));
    }

    public function testAddUserToGroupNotFound(): void
    {
        $groupResp = $this->setupMocks('GET', '/groups', 200, [['name' => 'Other', 'id' => 'gid']]);
        $this->setupCallback([['method' => 'GET', 'url' => '/groups', 'response' => $groupResp]]);
        $this->assertFalse($this->service->addUserToGroup('uid', 'G'));
    }

    public function testDeleteUser(): void
    {
        $resp = $this->setupMocks('DELETE', '/users/uid', 204);
        $this->setupCallback([['method' => 'DELETE', 'url' => '/users/uid', 'response' => $resp]]);
        $this->assertTrue($this->service->deleteUser('uid'));
    }

    public function testUpdateUser(): void
    {
        $resp = $this->setupMocks('PUT', '/users/uid', 204);
        $this->setupCallback([['method' => 'PUT', 'url' => '/users/uid', 'response' => $resp]]);
        $this->assertTrue($this->service->updateUser('uid', 'F', 'L', 'E'));
    }

    public function testResetPassword(): void
    {
        $resp = $this->setupMocks('PUT', '/reset-password', 204);
        $this->setupCallback([['method' => 'PUT', 'url' => '/reset-password', 'response' => $resp]]);
        $this->assertTrue($this->service->resetPassword('uid', 'p'));
    }

    public function testSendResetPasswordEmail(): void
    {
        $resp = $this->setupMocks('PUT', '/execute-actions-email', 204);
        $this->setupCallback([['method' => 'PUT', 'url' => '/execute-actions-email', 'response' => $resp]]);
        $this->assertTrue($this->service->sendResetPasswordEmail('uid'));
    }

    public function testGetUserGroups(): void
    {
        $resp = $this->setupMocks('GET', '/groups', 200, [['name' => 'G1'], ['name' => 'G2']]);
        $this->setupCallback([['method' => 'GET', 'url' => '/users/uid/groups', 'response' => $resp]]);
        $this->assertEquals(['G1', 'G2'], $this->service->getUserGroups('uid'));
    }

    public function testListUsers(): void
    {
        $resp = $this->setupMocks('GET', '/users', 200, [['username' => 'u1']]);
        $this->setupCallback([['method' => 'GET', 'url' => '/users', 'response' => $resp]]);
        $this->assertEquals([['username' => 'u1']], $this->service->listUsers());
    }

    public function testGetUserIdByEmailNotFound(): void
    {
        $resp = $this->setupMocks('GET', '/users', 200, []);
        $this->setupCallback([['method' => 'GET', 'url' => '/users', 'response' => $resp]]);
        $this->assertNull($this->service->getUserIdByEmail('t@e.com'));
    }

    public function testListGroups(): void
    {
        $resp = $this->setupMocks('GET', '/groups', 200, [['id' => 'g1', 'name' => 'ecosplay_admin'], ['id' => 'g2', 'name' => 'esy-web']]);
        $this->setupCallback([['method' => 'GET', 'url' => '/groups', 'response' => $resp]]);
        $groups = $this->service->listGroups();
        $this->assertCount(2, $groups);
        $this->assertSame('ecosplay_admin', $groups[0]['name']);
    }

    public function testCreateGroup(): void
    {
        $resp = $this->setupMocks('POST', '/groups', 201);
        $this->setupCallback([['method' => 'POST', 'url' => '/groups', 'response' => $resp]]);
        $this->assertTrue($this->service->createGroup('test-group'));
    }

    public function testCreateGroupFailure(): void
    {
        $resp = $this->setupMocks('POST', '/groups', 409);
        $this->setupCallback([['method' => 'POST', 'url' => '/groups', 'response' => $resp]]);
        $this->assertFalse($this->service->createGroup('test-group'));
    }

    public function testGetRequiredGroups(): void
    {
        $groups = KeycloakAdminService::getRequiredGroups();
        $this->assertContains('superadmin', $groups);
        $this->assertContains('gp_member', $groups);
        $this->assertContains('gp_ndd', $groups);
        $this->assertContains('gp_mail', $groups);
        $this->assertCount(10, $groups);
    }

    public function testEnsureRequiredGroupsAllExist(): void
    {
        $existingGroups = array_map(fn ($name) => ['id' => 'id_'.$name, 'name' => $name], KeycloakAdminService::getRequiredGroups());
        $resp = $this->setupMocks('GET', '/groups', 200, $existingGroups);
        $this->setupCallback([['method' => 'GET', 'url' => '/groups', 'response' => $resp]]);

        $created = $this->service->ensureRequiredGroups();
        $this->assertSame([], $created);
    }

    public function testEnsureRequiredGroupsCreatesMissing(): void
    {
        $listResp = $this->setupMocks('GET', '/groups', 200, [['id' => 'g1', 'name' => 'ecosplay_admin']]);
        $createResp = $this->setupMocks('POST', '/groups', 201);
        $this->setupCallback([
            ['method' => 'GET', 'url' => '/groups', 'response' => $listResp],
            ['method' => 'POST', 'url' => '/groups', 'response' => $createResp],
        ]);

        $created = $this->service->ensureRequiredGroups();
        $this->assertNotEmpty($created);
        $this->assertNotContains('ecosplay_admin', $created);
    }

    public function testTokenCaching(): void
    {
        $tokenResp = $this->createStub(ResponseInterface::class);
        $tokenResp->method('toArray')->willReturn(['access_token' => 't1', 'expires_in' => 3600]);

        $getResp = $this->setupMocks('GET', '/users', 200, []);

        $httpClient = $this->createStub(HttpClientInterface::class);
        $httpClient->method('request')
            ->willReturnCallback(function($m, $u) use ($tokenResp, $getResp) {
                 if ($m === 'POST' && str_contains($u, '/token')) return $tokenResp;
                 return $getResp;
            });

        $service = new KeycloakAdminService($httpClient, 'http://k', 'r', 'c', 's');
        $service->listUsers();
        $this->assertEquals([], $service->listUsers()); // Should use cached token
    }
}
