<?php
    namespace Workbloom\Component\Auth\v0\Services;

    use DateTime;
    use Exception;
    use Ramsey\Uuid\Uuid;
    use Workbloom\Services\DB;
    use Workbloom\Services\JWTService;
    use Workbloom\Component\Auth\v0\DTO\UsuarioTokenDTO;
    use Workbloom\Component\Auth\v0\Models\UsuarioModel;

    class AuthService {
        /**
         * Realiza a autenticação de um usuário, verificando as credenciais, revogando sessões antigas e gerando um novo token JWT.
         *
         * Este método gerencia todo o fluxo de login e a criação da sessão de autenticação. Ele garante a atomicidade das operações de banco de dados (`revogação` e `registro do token`) utilizando transações.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * 2.  **Verificação de Credenciais:** Busca o usuário pelo login (e-mail ou nome de usuário) e verifica a senha fornecida contra o hash armazenado. Se falhar, lança uma `Exception` (código 401).
         * 3.  **Verificação de Status:** Confere se o usuário está ativo (`status_id = 1`) e não excluído. Se não, lança uma `Exception` (código 403).
         * 4.  **Geração do JWT:** Monta o payload do token com os dados essenciais do usuário e gera o novo token JWT.
         * 5.  **Revogação de Tokens Antigos:** Revoga todos os tokens ativos anteriores do usuário no banco de dados. Se a revogação falhar, a transação é revertida, e uma `Exception` é lançada.
         * 6.  **Registro do Novo Token:** Armazena o novo token JWT e metadados de sessão (IP, User Agent, expiração) no banco de dados. Se o registro falhar, a transação é revertida, e uma `Exception` é lançada.
         * 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 reverte a transação e retorna um array de erro formatado.
         *
         * @param array $credentials Um array associativo contendo as credenciais do usuário, com as chaves 'login' e 'password'.
         * @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(array $credentials): array {
            try {
                // Consulta o usuario com base no login
                $usuarioLogin = (new UsuarioModel())->findUsuarioByLogin(login: $credentials['login']);
                if($usuarioLogin['status'] !== 'success') {
                    throw new Exception(message: 'Usuário não encontrado', code: 404);
                }
                $usuarioData = $usuarioLogin['output']['data'];

                // Verifica a senha
                if(!password_verify(password: $credentials['password'], hash: $usuarioData['senha_hash'])) {
                    throw new Exception(message: 'Senha inválida', code: 401);
                }

                // Verifica se o usuário está ativo
                if(($usuarioData['status_id'] != 1) || ($usuarioData['deleted_at'] !== null)) {
                    throw new Exception(message: 'Usuário inativo ou excluído', code: 403);
                }

                // Monta o payload do token
                $payload['user_data'] = [
                    'uuid' => $usuarioData['uuid'],
                    'nome_completo' => $usuarioData['nome_completo'],
                    'nome_usuario' => $usuarioData['nome_usuario'],
                    'is_root' => $usuarioData['is_root']
                ];

                // Gera o token JWT
                $generateJWT = JWTService::generateToken(payload: $payload);

                // Inicia Transação no Banco de Dados
                DB::beginTransaction();

                // Revoga tokens de sessões anteriores
                $revokeOldTokens = (new UsuarioModel())->revokeOldUsuarioTokens(usuario_uuid: $usuarioData['uuid']);
                if($revokeOldTokens['status'] !== 'success') {
                    DB::rollBack();
                    throw new Exception(message: 'Erro ao revogar tokens antigos', code: 500);
                }

                // Registra o token da sessão
                $registerToken = (new UsuarioModel())->storeUsuarioToken(
                    usuarioTokenDTO: new UsuarioTokenDTO(data: [
                        'usuario_id' => $usuarioData['id'],
                        'usuario_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
                        'usuario_user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
                        'token' => $generateJWT['token'],
                        'is_revoked' => 0,
                        'expires_at' => $generateJWT['expires_at']
                    ])
                );
                if($registerToken['status'] !== 'success') {
                    DB::rollBack();
                    throw new Exception(message: 'Erro ao registrar token de sessão', code: 500);
                }

                DB::commit();
                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Login realizado com sucesso',
                    'output' => [
                        'data' => $generateJWT
                    ]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?? 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?? 'Internal server error'
                ];
            }
        }

        /**
         * Valida um token de autenticação JWT contra o registro do banco de dados e verifica a expiração/revogação.
         *
         * Este método realiza uma série de verificações de segurança para garantir que o token seja válido e não tenha sido comprometido ou expirado. Ele gerencia o processo de revogação do token no banco de dados se o token for encontrado, mas estiver expirado.
         *
         * #### Fluxo de Operação:
         * 1.  **Busca e Validação no DB:** Consulta o token no banco de dados. Se não for encontrado, lança `Exception` (código 404/401).
         * 2.  **Verificação de Expiração e Revogação:** Checa se o token está revogado (`is_revoked === 1`) ou se o prazo de validade (`expires_at`) foi ultrapassado.
         * 3.  **Revogação Automática (se Expirado):** Se o token estiver expirado, e ainda não estiver revogado, o método inicia uma transação (`DB::beginTransaction()`) para revogá-lo no DB. Em seguida, lança uma `Exception` com código `401 Unauthorized` (Token revogado).
         * 4.  **Verificação de Dispositivo:** Compara o IP (`usuario_ip`) e o User Agent (`usuario_user_agent`) armazenados no token com os da requisição atual. Se não houver correspondência, lança uma `Exception` (código 401), invalidando o token para o dispositivo.
         * 5.  **Retorno de Sucesso:** Se todas as verificações passarem, retorna `status` 'success' com o código `200 OK` e os dados do token.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada durante o processo é capturada, e o método retorna um array de erro formatado. O `DB::rollBack()` é tratado nos sub-métodos de revogação, mas o fluxo garante que o erro seja propagado.
         *
         * @param string $token O token JWT de autenticação 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 validateAuthToken(string $token) {
            try {
                // Consulta o token no banco de dados
                $findToken = (new UsuarioModel())->findUsuarioTokenByIdentifier(identifier: 'token', value: $token);
                if($findToken['status'] !== 'success') {
                    throw new Exception(message: 'Token não encontrado', code: 404);
                }
                $tokenData = $findToken['output']['data'];

                // Verifica se o token está revogado ou expirado
                if(($tokenData['is_revoked'] === 1) || (new DateTime() > new DateTime(datetime: $tokenData['expires_at']))) {
                    
                    // Revoga o token se ainda não estiver revogado
                    if($tokenData['is_revoked'] === 0) {
                        // Incia Transação no Banco de Dados
                        DB::beginTransaction();

                        // Atualiza o token para revogado
                        $revokeToken = (new UsuarioModel())->updateUsuarioToken(usuarioTokenDTO: new UsuarioTokenDTO(data: [
                            'is_revoked' => 1,
                            'revoked_at' => (new DateTime())->format(format: 'Y-m-d H:i:s'),
                            'token' => $tokenData['token']
                        ]));

                        // Verifica se a revogação foi bem-sucedida
                        if($revokeToken['status'] !== 'success') {
                            DB::rollBack();
                            throw new Exception(message: 'Erro ao revogar token', code: 500);
                        }

                        // Finaliza Transação no Banco de Dados
                        DB::commit();
                    }

                    // Lança exceção indicando que o token foi revogado
                    throw new Exception(message: 'Token revogado', code: 401);
                }

                // Verifica se o agente do usuário e o IP correspondem ao da requisição atual
                if(isset($tokenData['usuario_ip']) && !in_array(needle: $tokenData['usuario_ip'], haystack: [$_SERVER['REMOTE_ADDR'], null], strict: true)) {
                    throw new Exception(message: "Token inválido para este dispositivo ou IP", code: 401);
                }
                if(isset($tokenData['usuario_user_agent']) && !in_array(needle: $tokenData['usuario_user_agent'], haystack: [$_SERVER['HTTP_USER_AGENT'], null], strict: true)) {
                    throw new Exception(message: "Token inválido para este dispositivo ou IP", code: 401);
                }

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Token válido',
                    'output' => [
                        'data' => $tokenData
                    ]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?? 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?? 'Internal server error'
                ];
            }
        }

        /**
         * Lista os contextos de trabalho (empresas) disponíveis para um usuário.
         *
         * Este método determina quais empresas o usuário autenticado pode acessar. Se o usuário for marcado como 'root' (`$is_root` = 1), ele lista todos os contextos ativos no sistema. Caso contrário, lista apenas os contextos associados diretamente ao seu UUID.
         *
         * #### Fluxo de Operação:
         * 1.  **Verificação de Permissão Root:**
         * - Se `$is_root` for `1`, chama `UsuarioModel::findAllWorkContexts()` para listar todos os contextos ativos do sistema.
         * - Se a consulta falhar, lança uma `Exception` (código 500).
         * 2.  **Busca por Usuário Específico:**
         * - Se o usuário não for root, chama `UsuarioModel::findWorkContextsByUsuarioUUID()` para listar apenas os contextos diretamente associados ao `usuario_uuid`.
         * - Se a consulta falhar, lança uma `Exception` (código 500).
         * 3.  **Retorno:** Retorna o array de resultados de contextos (sucesso ou falha, conforme retornado pelo modelo).
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` capturada durante o processo (incluindo falhas nos métodos do modelo) retorna um array de erro formatado com o código HTTP e a mensagem da exceção.
         *
         * @param string $usuario_uuid O UUID do usuário autenticado.
         * @param int $is_root Indica se o usuário possui permissão de root (1) ou não (0).
         * @return array Um array associativo contendo o status da operação, código HTTP e os dados dos contextos de trabalho (`output` com `data`), ou um erro.
         */
        public function listWorkContexts(string $usuario_uuid, int $is_root = 0): array {
            try {
                // Se for root, lista todos os contextos de trabalho
                if($is_root) {
                    // Consulta os contextos de trabalho do usuário
                    $contextos = (new UsuarioModel())->findAllWorkContexts(is_root: $is_root);
                    if($contextos['status'] !== 'success') {
                        throw new Exception(message: 'Erro ao recuperar contextos de trabalho', code: 500);
                    }

                    // Lógica para listar todos os contextos de trabalho
                    return $contextos;
                }

                // Consulta os contextos de trabalho do usuário
                $contextos = (new UsuarioModel())->findWorkContextsByUsuarioUUID(usuario_uuid: $usuario_uuid);
                if($contextos['status'] !== 'success') {
                    throw new Exception(message: 'Erro ao recuperar contextos de trabalho', code: 500);
                }

                // Lógica para listar os contextos de trabalho do usuário
                return $contextos;
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?? 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?? 'Internal server error'
                ];
            }
        }

        /**
         * Seleciona e ativa um contexto de trabalho (empresa) para o usuário autenticado.
         *
         * Este método é chamado após o login para reemitir um token JWT que inclui as informações do contexto de trabalho selecionado. A operação revoga o token antigo da sessão atual e registra um novo, garantindo a atomicidade das operações de banco de dados.
         *
         * #### Fluxo de Operação:
         * 1.  **Verificação de Permissão:** Se o usuário não for 'root' (`$is_root === 0`), verifica se o usuário tem permissão para acessar o `contexto_uuid` específico. Se a verificação falhar, lança uma `Exception` (código 404).
         * 2.  **Busca de Dados:** Busca os dados completos do usuário e os dados do contexto (se não for root).
         * 3.  **Montagem do Payload:** Monta o payload do novo token JWT, incluindo os dados básicos do usuário e o novo `context_data` (UUID e informações da empresa, se aplicável).
         * 4.  **Gerenciamento Transacional:**
         * - Inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * - **Revoga Token Antigo:** Atualiza o token anterior para revogado. Se falhar, reverte a transação e lança `Exception` (código 500).
         * - **Gera e Registra Novo Token:** Gera um novo token JWT com o payload atualizado e o registra no banco de dados. Se falhar, reverte a transação e lança `Exception` (código 500).
         * 5.  **Confirmação e Retorno:** A transação é confirmada (`DB::commit()`), e o novo token é retornado com `status` 'success' e código `200 OK`.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada é capturada, e o método retorna um array de erro formatado com o código HTTP e a mensagem da exceção.
         *
         * @param string $usuario_uuid O UUID do usuário autenticado.
         * @param string $contexto_uuid O UUID do contexto de trabalho (empresa) selecionado.
         * @param int $is_root Flag que indica se o usuário é root (0 ou 1).
         * @return array Um array associativo contendo o status da operação, o código de resposta HTTP e o novo token JWT.
         */
        public function selectWorkContext(string $usuario_uuid, string $contexto_uuid, int $is_root = 0): array {
            try {
                // Se não for root, verifica se o contexto de trabalho pertence ao usuário
                if($is_root === 0) {
                    // Consulta se o contexto de trabalho pertence ao usuário
                    $workContext = (new UsuarioModel())->findWorkContextByUsuarioAndContextoUUID(
                        usuario_uuid: $usuario_uuid,
                        contexto_uuid: $contexto_uuid
                    );
                    if($workContext['status'] !== 'success') {
                        throw new Exception(message: 'Contexto de trabalho não encontrado para o usuário', code: 404);
                    }
                }

                // Consulta os dados do usuário
                $usuarioLogin = (new UsuarioModel())->findUsuarioByIdentifier(identifier: 'uuid', value: $usuario_uuid);
                if($usuarioLogin['status'] !== 'success') {
                    throw new Exception(message: 'Usuário não encontrado', code: 404);
                }
                $usuarioData = $usuarioLogin['output']['data'];

                // Monta novo payload do token com o contexto selecionado
                $payload['user_data'] = [
                    'uuid' => $usuarioData['uuid'],
                    'nome_completo' => $usuarioData['nome_completo'],
                    'nome_usuario' => $usuarioData['nome_usuario'],
                    'is_root' => $usuarioData['is_root']
                ];

                // Monta novo payload do token com o contexto selecionado
                if(isset($workContext)) {
                    $payload['context_data'] = [
                        'uuid' => $workContext['output']['data']['contexto_uuid']
                    ];

                    if($workContext['output']['data']['razao_social'] !== null) {
                        $payload['context_data']['razao_social'] = $workContext['output']['data']['razao_social'];
                    }

                    if($workContext['output']['data']['nome_fantasia'] !== null) {
                        $payload['context_data']['nome_fantasia'] = $workContext['output']['data']['nome_fantasia'];
                    }
                } else {
                    $payload['context_data'] = [
                        'uuid' => $contexto_uuid
                    ];
                }

                // Inicia Transação no Banco de Dados
                DB::beginTransaction();

                // Gera o token JWT
                $generateJWT = JWTService::generateToken(payload: $payload);

                // Revoga o token antigo
                $revokeToken = (new UsuarioModel())->updateUsuarioToken(usuarioTokenDTO: new UsuarioTokenDTO(data: [
                    'is_revoked' => 1,
                    'revoked_at' => (new DateTime())->format(format: 'Y-m-d H:i:s'),
                    'token' => JWTService::getBearerToken()
                ]));
                if($revokeToken['status'] !== 'success') {
                    DB::rollBack();
                    throw new Exception(message: 'Erro ao revogar token antigo', code: 500);
                }

                // Registra o novo token da sessão
                $registerToken = (new UsuarioModel())->storeUsuarioToken(
                    usuarioTokenDTO: new UsuarioTokenDTO(data: [
                        'usuario_id' => $usuarioData['id'],
                        'usuario_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
                        'usuario_user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
                        'token' => $generateJWT['token'],
                        'is_revoked' => 0,
                        'expires_at' => $generateJWT['expires_at']
                    ])
                );
                if($registerToken['status'] !== 'success') {
                    DB::rollBack();
                    throw new Exception(message: 'Erro ao registrar token de sessão', code: 500);
                }

                // Confirma a transação no banco de dados
                DB::commit();
                
                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Contexto de trabalho selecionado com sucesso',
                    'output' => ['data' => $generateJWT]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?? 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?? 'Internal server error'
                ];
            }
        }

        /**
         * Finaliza a sessão do usuário revogando um token de autenticação JWT específico.
         *
         * Este método atua como um **serviço**, orquestrando a revogação do token no banco de dados e garantindo a atomicidade da operação.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * 2.  **Preparação do DTO:** Cria um `UsuarioTokenDTO` para a revogação, definindo o `token`, o status `is_revoked = 1` e o `revoked_at` como o timestamp atual.
         * 3.  **Revogação no Modelo:** Chama `UsuarioModel::updateUsuarioToken()` para invalidar o token no banco de dados.
         * 4.  **Gerenciamento de Transação:**
         * * Se a revogação falhar (status do modelo for `!== 'success'`), a transação é **revertida** (`DB::rollBack()`), e uma `Exception` é lançada.
         * * Se a revogação for bem-sucedida, a transação é **confirmada** (`DB::commit()`).
         * 5.  **Retorno de Sucesso:** Retorna um array com **`status` 'success'** e código **200 OK**.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada durante o processo é capturada, a transação é revertida, e o método retorna um array de erro formatado com 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, o código de resposta HTTP e uma mensagem.
         */
        public function logout(string $token): array {
            try {
                // Inicia Transação no Banco de Dados
                DB::beginTransaction();

                // Atualiza o token para revogado
                $revokeToken = (new UsuarioModel())->updateUsuarioToken(usuarioTokenDTO: new UsuarioTokenDTO(data: [
                    'is_revoked' => 1,
                    'revoked_at' => (new DateTime())->format(format: 'Y-m-d H:i:s'),
                    'token' => $token
                ]));
                if($revokeToken['status'] !== 'success') {
                    DB::rollBack();
                    throw new Exception(message: 'Erro ao revogar token', code: 500);
                }

                // Confirma a transação no banco de dados
                DB::commit();
                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Logout realizado com sucesso'
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => $e->getCode() ?? 500,
                    'status' => 'error',
                    'message' => $e->getMessage() ?? 'Internal server error'
                ];
            }
        }
    }