<?php
    namespace Zampet\Module\Auth\v0\Services;

    use DateTime;
    use Exception;
    use Ramsey\Uuid\Uuid;
    use Zampet\Services\DB;
    use Zampet\Services\JWTService;
    use Zampet\Helpers\DataValidator;
    use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
    use Zampet\Module\Auth\v0\Models\UsuarioModel;

    class AuthService {
        /**
         * Registra um novo usuário no sistema após realizar validações rigorosas.
         *
         * Este método orquestra o processo de criação de um novo usuário, incluindo validação de dados, checagem de duplicidade de e-mail e persistência no banco de dados. A operação é atômica e utiliza uma transação de banco de dados (`DB::beginTransaction()`).
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados.
         * 2.  **Validação de Dados:** Um `DataValidator` é usado para validar campos obrigatórios (`nome_completo`, `email`, `senha`) e a complexidade da senha (mínimo de 8 caracteres, maiúsculas, números e símbolos).
         * 3.  **Checagem de Duplicidade:** O método `getUsuarioByIdentifier` é chamado para verificar se o e-mail já está em uso por outro usuário.
         * 4.  **Encerramento em Caso de Falha na Validação/Duplicidade:** Se houver erros de validação (código **400**) ou se o e-mail já estiver em uso (código **409**), a transação é revertida (`DB::rollBack()`) e o erro é retornado.
         * 5.  **Preparação Final dos Dados:** Se a validação passar, o **UUID** do usuário e o timestamp de criação são definidos. A senha é criptografada usando **`PASSWORD_ARGON2ID`** antes de ser persistida.
         * 6.  **Persistência:** O método `store` do `UsuarioModel` é chamado para inserir o novo registro.
         * 7.  **Confirmação:** Se a persistência for bem-sucedida (código **201**), a transação é confirmada (`DB::commit()`) e o resultado de sucesso é retornado.
         * 8.  **Tratamento de Erro de Persistência:** Se o `store` falhar por um motivo inesperado (código **500**), a transação é revertida.
         *
         * @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do novo usuário, incluindo nome, email e senha.
         * @return array Um array associativo contendo o código HTTP, status da operação, mensagem e dados de saída.
         */
        public function register(UsuarioDTO $usuarioDTO): array {
            // Inicia conexão com o banco de dados
            DB::beginTransaction();

            // Valida os dados do DTO
            $dataValidador = new DataValidator(inputs: $usuarioDTO->toArray());
            $dataValidador->validate(field: 'nome_completo', rule: 'notEmpty', message: 'Nome completo é obrigatório');
            $dataValidador->validate(field: 'email', rule: 'notEmpty', message: 'Email é obrigatório');
            $dataValidador->validate(field: 'email', rule: 'validateEmail', message: 'Email inválido');
            $dataValidador->validate(field: 'senha', rule: 'notEmpty', message: 'Senha é obrigatória');
            $dataValidador->validate(field: 'documentcpf', rule: 'notEmpty', message: 'CPF é obrigatório');
            $dataValidador->validate(field: 'documentcpf', rule: 'validateCpf', message: 'CPF informado é inválido.');
            $dataValidador->validate(field: 'senha', rule: 'validatePassword', message: 'Senha inválida', additionalParams: [
                    [
                        'minLength' => 8,
                        'upper' => true,
                        'digit' => true,
                        'symbol' => true
                    ]
                ]
            );

            // Se houver erros de validação, retornar resposta de erro
            if(!$dataValidador->passes()) {
                DB::rollBack();
                return [
                    'response_code' => 400,
                    'status' => 'error',
                    'message' => 'Validation errors',
                    'output' => [
                        'errors' => $dataValidador->getErrors()
                    ]
                ];
            }

            // Verifica se o email já está em uso
            $existingEmailUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $usuarioDTO->getEmail());
            if($existingEmailUser['status'] === 'success' && !empty($existingEmailUser['output']['data'])) {
                DB::rollBack();
                return [
                    'response_code' => 409,
                    'status' => 'error',
                    'message' => 'E-mail já está em uso por outro usuário.',
                    'output' => []
                ];
            }

            // Verifica se o CPF já está em uso
            $existingCpfUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'documentcpf', value: $usuarioDTO->getDocumentCpf());
            if($existingCpfUser['status'] === 'success' && !empty($existingCpfUser['output']['data'])) {
                DB::rollBack();
                return [
                    'response_code' => 409,
                    'status' => 'error',
                    'message' => 'CPF já está em uso por outro usuário.',
                    'output' => []
                ];
            }

            // Define dados adicionais
            $usuarioDTO->setUuid(uuid: Uuid::uuid7()->toString());
            $usuarioDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));
            $usuarioDTO->setSenha(senha: password_hash(password: $usuarioDTO->getSenha(), algo: PASSWORD_ARGON2ID));

            // Persiste o novo usuário no banco de dados
            $storeUsuario = (new UsuarioModel())->store(usuarioDTO: $usuarioDTO);
            if($storeUsuario['status'] !== 'success') {
                DB::rollBack();
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Erro ao registrar usuário. Tente novamente mais tarde.',
                    'output' => $storeUsuario['output'] ?? []
                ];
            }

            DB::commit();
            return [
                'response_code' => $storeUsuario['response_code'],
                'status' => $storeUsuario['status'],
                'message' => $storeUsuario['message'],
                'output' => $storeUsuario['output']
            ];
        }

        /**
         * Realiza a autenticação de um usuário por e-mail e senha, gerando e registrando um token JWT.
         *
         * Este método gerencia todo o fluxo de login, incluindo a verificação de credenciais, revogação de tokens antigos, geração de um novo JWT e persistência desse novo token no banco de dados. A operação é atômica e utiliza uma transação de banco de dados para garantir a consistência dos dados.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * 2.  **Consulta do Usuário:** Busca o usuário pelo e-mail. Se o usuário não for encontrado, uma `Exception` é lançada com o código `401 Unauthorized`.
         * 3.  **Validação da Senha:** Verifica a senha fornecida contra o hash armazenado. Se a senha for inválida, uma `Exception` é lançada com o código `401 Unauthorized`.
         * 4.  **Revogação de Tokens Antigos:** Chama o método `revokeOldTokens` do `UsuarioModel` para invalidar todas as sessões anteriores do usuário.
         * 5.  **Geração do JWT:** Monta o payload do JWT com os dados essenciais do usuário e os timestamps `iat` (emitido em) e `exp` (expira em), gerando o novo token.
         * 6.  **Registro do Token:** Armazena o novo token no banco de dados usando `storeToken`. Se a operação falhar, uma `Exception` é lançada com o código `500 Internal Server Error`.
         * 7.  **Confirmação e Retorno:** Se todas as etapas forem bem-sucedidas, a transação é confirmada (`DB::commit()`), e o token gerado é retornado com `status` 'success' e código `200 OK`.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollBack()`).
         * - Retorna um array com `status` 'error', o código de erro da exceção (ou 500 como fallback) e uma mensagem descritiva.
         *
         * @param string $email O e-mail do usuário para autenticação.
         * @param string $password A senha em texto plano fornecida pelo usuário.
         * @return array Um array associativo contendo o status da operação, o código de resposta HTTP e o token JWT em caso de sucesso.
         */
        public function login(string $email, string $password): array {
            try {
                // Inicia Transação no Banco de dados
                DB::beginTransaction();

                // Consulta as informações do usuário pelo email
                $userResult = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $email);
                if($userResult['status'] !== 'success' || empty($userResult['output']['data'])) {
                    throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
                }

                // Valida a senha fornecida com a armazenada
                if(!password_verify(password: $password, hash: $userResult['output']['data']['senha'])) {
                    throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
                }

                // Revoga os tokens antigos do usuário
                $revokeOldTokens = (new UsuarioModel())->revokeOldTokens(user_id: $userResult['output']['data']['id']);
                if($revokeOldTokens['status'] !== 'success') {
                    throw new Exception(message: 'Erro ao revogar tokens antigos.', code: 500);
                }

                // Prepara o Payload do JWT
                $jwtPayload = [
                    'user_uuid' => $userResult['output']['data']['uuid'],
                    'email' => $userResult['output']['data']['email'],
                    'nome_completo' => $userResult['output']['data']['nome_completo'],
                    'iat' => (new DateTime())->getTimestamp(),
                    'exp' => (new DateTime())->modify(modifier: '+8 hour')->getTimestamp()
                ];

                $token = JWTService::generateToken(payload: $jwtPayload);

                // Armazena o token no banco de dados
                $storeToken = (new UsuarioModel())->storeToken(user_id: $userResult['output']['data']['id'], token: $token);
                if($storeToken['status'] !== 'success') {
                    throw new Exception(message: 'Erro ao armazenar token de autenticação.', code: 500);
                }

                DB::commit();
                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Login successful.',
                    'output' => [
                        'data' => [
                            'token' => $token
                        ]
                    ]
                ];
            } catch(Exception $e) {
                DB::rollBack();
                return [
                    'response_code' => $e->getCode() ?: 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?: 'An unexpected error occurred during login.'
                ];
            }
        }

        /**
         * Finaliza a sessão do usuário revogando um token JWT específico.
         *
         * Este método é o responsável por orquestrar o processo de logout seguro, garantindo que o token de autenticação seja validado e, em seguida, invalidado no banco de dados.
         *
         * #### Fluxo de Operação:
         * 1.  **Validação do Token:** O método chama `validateToken` para verificar se o token existe e ainda é válido (não expirado ou revogado no DB). Se a validação falhar, uma `Exception` é lançada com o código de erro apropriado.
         * 2.  **Revogação no Banco de Dados:** Tenta revogar o token usando `UsuarioModel::revokeToken()`. Se a revogação falhar (ex: erro de banco de dados), uma `Exception` é lançada.
         * 3.  **Retorno de Sucesso:** Se ambas as operações (validação e revogação) forem bem-sucedidas, retorna um array com **`status` 'success'**, código **200 OK** e uma mensagem de êxito.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada durante o processo é capturada, e o método retorna um array de erro formatado, contendo o código HTTP e a mensagem da exceção.
         *
         * @param string $token O token JWT a ser revogado.
         * @return array Um array associativo contendo o status da operação ("success" ou "error"), o código de resposta HTTP e uma mensagem.
         */
        public function logout(string $token): array {
            try {
                // Validar se o token existe e não está revogado
                $validateToken = $this->validateToken(token: $token);
                if($validateToken['status'] !== 'success') {
                    throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
                }

                // Revoga o token fornecido
                $revokeToken = (new UsuarioModel())->revokeToken(token: $token);
                if($revokeToken['status'] !== 'success') {
                    throw new Exception(message: 'Erro ao revogar token.', code: $revokeToken['response_code']);
                }

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Logout successful.',
                    'output' => []
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?: 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?: 'An unexpected error occurred during logout.'
                ];
            }
        }

        /**
         * Valida um token de autenticação verificando sua presença no banco de dados e seu status de expiração.
         *
         * Este método realiza uma validação de duas etapas: 1) Checa se o token existe e não está revogado no banco de dados, e 2) verifica se o timestamp de expiração (exp) no payload do token já passou. Se o token estiver expirado, ele é revogado no banco de dados.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`) para garantir que a revogação, se necessária, seja atômica.
         * 2.  **Busca no DB:** Tenta encontrar o token usando o `UsuarioModel`. Se a busca falhar (token não encontrado ou revogado), uma `Exception` é lançada com o código `401 Unauthorized`.
         * 3.  **Decodificação:** O token é decodificado usando `JWTService::decode()` para acessar o payload e o timestamp de expiração (`exp`).
         * 4.  **Verificação de Expiração:** Compara o timestamp atual com o `exp` do payload.
         * * **Se Expirado:** O método tenta revogar o token no DB. Se a revogação for bem-sucedida, a transação é confirmada e o método retorna um erro com código `401`. Se a revogação falhar, a transação é revertida e um erro `500` é retornado.
         * * **Se Válido:** A transação é confirmada e o método retorna `status` 'success' com o código `200 OK` e os dados do payload.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada durante o processo (falha de busca, falha de decodificação JWT, falha de revogação) é capturada. Se a falha ocorrer após o início da transação, um `DB::rollBack()` é implícito ou explicitamente chamado antes do retorno.
         * - Retorna um array com `status` 'error', o código de erro (ou `500` como fallback) e uma mensagem descritiva.
         *
         * @param string $token O token JWT a ser validado.
         * @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se válido.
         */
        public function validateToken(string $token): array {
            // Inicia transação no banco de dados
            DB::beginTransaction();

            try {
                $dbTokenData = (new UsuarioModel())->getTokenByidentifier(identifier: 'token', value: $token);
                if($dbTokenData['status'] !== 'success' || empty($dbTokenData['output']['data'])) {
                    throw new Exception(message: 'Token inválido ou expirado.', code: 401);
                }

                // Decodifica o token para verificar sua validade
                $decodedToken = JWTService::decode(token: $dbTokenData['output']['data']['token']);

                // Verifica se o token expirou
                $currentTimestamp = (new DateTime())->getTimestamp();
                switch (true) {
                    case $currentTimestamp > $decodedToken['exp']:
                        // Revoga o token expirado
                        $revokeToken = (new UsuarioModel())->revokeToken(token: $dbTokenData['output']['data']['token']);
                        if($revokeToken['status'] !== 'success') {
                            DB::rollBack();
                            throw new Exception(message: 'Erro ao revogar token expirado.', code: 500);
                        }

                        DB::commit();
                        return [
                            'response_code' => 401,
                            'status' => 'error',
                            'message' => 'Token expirado.',
                            'output' => []
                        ];
                    
                    case $dbTokenData['output']['data']['revoked'] === 1:
                        DB::commit();
                        return [
                            'response_code' => 401,
                            'status' => 'error',
                            'message' => 'Token inválido ou expirado.',
                            'output' => []
                        ];
                }
                
                DB::commit();
                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Token is valid.',
                    'output' => ['token_data' => $decodedToken]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?: 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?: 'An unexpected error occurred during token validation.'
                ];
            }
        }

        public function getUserData(string $token): array {
            try {
                // Validar se o token existe e não está revogado
                $validateToken = $this->validateToken(token: $token);
                if($validateToken['status'] !== 'success') {
                    throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
                }

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'User data retrieved successfully.',
                    'output' => [
                        'data' => $validateToken['output']['token_data']
                    ]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?: 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?: 'An unexpected error occurred while retrieving user data.'
                ];
            }
        }
    }