<?php

namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class KeycloakAdminService
{
    private const PATH_USERS = '/users';
    private const PATH_GROUPS = '/groups';
    private const AUTH_BEARER = 'Bearer ';

    /** Groupes requis pour le CRM E-Cosplay */
    private const REQUIRED_GROUPS = [
        'superadmin',
        'super_admin_asso',
        'gp_asso',
        'gp_contest',
        'gp_mail',
        'gp_mailling',
        'gp_member',
        'gp_ndd',
        'gp_sign',
        'gp_ticket',
    ];

    private ?string $accessToken = null;
    private ?int $tokenExpiresAt = null;

    public function __construct(
        private HttpClientInterface $httpClient,
        #[Autowire(env: 'OAUTH_KEYCLOAK_URL')] private string $keycloakUrl,
        #[Autowire(env: 'OAUTH_KEYCLOAK_REALM')] private string $realm,
        #[Autowire(env: 'KEYCLOAK_ADMIN_CLIENT_ID')] private string $clientId,
        #[Autowire(env: 'KEYCLOAK_ADMIN_CLIENT_SECRET')] private string $clientSecret,
    ) {
    }

    /**
     * Verifie que tous les groupes requis existent dans Keycloak, cree ceux qui manquent.
     *
     * @return list<string> liste des groupes crees
     */
    public function ensureRequiredGroups(): array
    {
        $existingGroups = $this->listGroups();
        $existingNames = array_map(fn (array $g) => $g['name'], $existingGroups);
        $created = [];

        foreach (self::REQUIRED_GROUPS as $groupName) {
            if (!\in_array($groupName, $existingNames, true) && $this->createGroup($groupName)) {
                $created[] = $groupName;
            }
        }

        return $created;
    }

    /**
     * Creer un groupe dans Keycloak.
     */
    public function createGroup(string $groupName): bool
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('POST', $this->getAdminUrl(self::PATH_GROUPS), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'json' => ['name' => $groupName],
        ]);

        return 201 === $response->getStatusCode();
    }

    /**
     * Creer un utilisateur dans Keycloak.
     *
     * @return array{created: bool, keycloakId: string|null, tempPassword: string|null}
     */
    public function createUser(string $email, string $firstName, string $lastName): array
    {
        $token = $this->getToken();
        $tempPassword = bin2hex(random_bytes(8));

        // Creer l'utilisateur
        $response = $this->httpClient->request('POST', $this->getAdminUrl(self::PATH_USERS), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'json' => [
                'username' => $email,
                'email' => $email,
                'firstName' => $firstName,
                'lastName' => $lastName,
                'enabled' => true,
                'emailVerified' => true,
                'requiredActions' => ['UPDATE_PASSWORD', 'CONFIGURE_TOTP'],
                'credentials' => [
                    [
                        'type' => 'password',
                        'value' => $tempPassword,
                        'temporary' => true,
                    ],
                ],
            ],
        ]);

        if (201 !== $response->getStatusCode()) {
            return ['created' => false, 'keycloakId' => null, 'tempPassword' => null];
        }

        // Recuperer l'ID du user cree
        $locationHeader = $response->getHeaders(false)['location'][0] ?? '';
        $keycloakId = basename($locationHeader);

        if ('' === $keycloakId) {
            $keycloakId = $this->getUserIdByEmail($email);
        }

        return [
            'created' => true,
            'keycloakId' => $keycloakId,
            'tempPassword' => $tempPassword,
        ];
    }

    /**
     * Ajouter un utilisateur a un groupe Keycloak.
     */
    public function addUserToGroup(string $keycloakId, string $groupName): bool
    {
        $groupId = $this->getGroupIdByName($groupName);

        if (null === $groupId) {
            return false;
        }

        $token = $this->getToken();
        $response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/groups/'.$groupId), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
        ]);

        return 204 === $response->getStatusCode();
    }

    /**
     * Supprimer un utilisateur de Keycloak.
     */
    public function deleteUser(string $keycloakId): bool
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('DELETE', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
        ]);

        return 204 === $response->getStatusCode();
    }

    /**
     * Mettre a jour les infos d'un utilisateur dans Keycloak.
     */
    public function updateUser(string $keycloakId, string $firstName, string $lastName, string $email): bool
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'json' => [
                'firstName' => $firstName,
                'lastName' => $lastName,
                'email' => $email,
                'username' => $email,
            ],
        ]);

        return 204 === $response->getStatusCode();
    }

    /**
     * Reset le mot de passe d'un utilisateur dans Keycloak.
     */
    public function resetPassword(string $keycloakId, string $newPassword): bool
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/reset-password'), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'json' => [
                'type' => 'password',
                'value' => $newPassword,
                'temporary' => false,
            ],
        ]);

        return 204 === $response->getStatusCode();
    }

    /**
     * Envoyer un email de reset password a l'utilisateur.
     */
    public function sendResetPasswordEmail(string $keycloakId): bool
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/execute-actions-email'), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'json' => ['UPDATE_PASSWORD'],
        ]);

        return 200 === $response->getStatusCode() || 204 === $response->getStatusCode();
    }

    /**
     * Recuperer les groupes d'un utilisateur.
     *
     * @return list<string>
     */
    public function getUserGroups(string $keycloakId): array
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/groups'), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
        ]);

        $groups = $response->toArray(false);

        return array_map(fn (array $g) => $g['name'], $groups);
    }

    /**
     * Lister tous les utilisateurs du realm.
     *
     * @return list<array<string, mixed>>
     */
    public function listUsers(int $max = 100): array
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_USERS), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'query' => ['max' => $max],
        ]);

        return $response->toArray(false);
    }

    /**
     * Recuperer l'ID d'un utilisateur par email.
     */
    public function getUserIdByEmail(string $email): ?string
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_USERS), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'query' => ['email' => $email, 'exact' => 'true'],
        ]);

        $users = $response->toArray(false);

        return $users[0]['id'] ?? null;
    }

    /**
     * Lister tous les groupes du realm.
     *
     * @return list<array{id: string, name: string}>
     */
    public function listGroups(): array
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_GROUPS), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
        ]);

        return $response->toArray(false);
    }

    /**
     * @return list<string>
     */
    public static function getRequiredGroups(): array
    {
        return self::REQUIRED_GROUPS;
    }

    private function getGroupIdByName(string $groupName): ?string
    {
        $token = $this->getToken();
        $response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_GROUPS), [
            'headers' => ['Authorization' => self::AUTH_BEARER.$token],
            'query' => ['search' => $groupName, 'exact' => 'true'],
        ]);

        $groups = $response->toArray(false);

        foreach ($groups as $group) {
            if ($group['name'] === $groupName) {
                return $group['id'];
            }
        }

        return null;
    }

    private function getToken(): string
    {
        if (null !== $this->accessToken && null !== $this->tokenExpiresAt && time() < $this->tokenExpiresAt) {
            return $this->accessToken;
        }

        $response = $this->httpClient->request('POST', $this->keycloakUrl.'/realms/'.$this->realm.'/protocol/openid-connect/token', [
            'body' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
            ],
        ]);

        $data = $response->toArray();
        $this->accessToken = $data['access_token'];
        $this->tokenExpiresAt = time() + ($data['expires_in'] ?? 300) - 30;

        return $this->accessToken;
    }

    private function getAdminUrl(string $path): string
    {
        return $this->keycloakUrl.'/admin/realms/'.$this->realm.$path;
    }
}
