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

    use DateTime;
    use Exception;
    use Zampet\Services\DB;
    use Zampet\Module\Auth\v0\DTO\UsuarioDTO;

    class UsuarioModel {
        protected string $usuarioTable = 'usuario';
        protected string $usuarioTokenTable = 'usuario_token';

        /**
         * Cria e armazena um novo usuário no banco de dados.
         *
         * Este método é responsável por inserir os dados completos de um novo usuário, incluindo informações pessoais, documentos, credenciais e endereço, na tabela `{$this->usuarioTable}`. 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.  **Execução da Query:** Prepara e executa a instrução `INSERT` com todos os campos preenchidos a partir do `UsuarioDTO`.
         * 3.  **Confirmação:** Se a inserção for bem-sucedida, a transação é confirmada (`DB::commit()`).
         * 4.  **Retorno de Sucesso:** Retorna um array com `status` 'success', o código `201 Created`, uma mensagem de êxito e os dados do usuário criado (ID e UUID).
         *
         * #### 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', código `500 Internal Server Error` e uma mensagem genérica de erro.
         *
         * @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do usuário a ser criado.
         * @return array Um array associativo com o status da operação, um código de resposta HTTP e uma mensagem.
         */
        public function store(UsuarioDTO $usuarioDTO): array {
            try {
                $sql = 
                "INSERT INTO {$this->usuarioTable} (
                    uuid,
                    nome_completo,
                    status_id,
                    documentcpf,
                    documentcrmv,
                    email,
                    senha,
                    telefone,
                    data_nascimento,
                    endereco_rua,
                    endereco_numero,
                    endereco_complemento,
                    endereco_bairro,
                    endereco_cidade,
                    endereco_uf,
                    endereco_cep,
                    created_at,
                    updated_at,
                    deleted_at
                ) VALUES (
                    :uuid,
                    :nome_completo,
                    :status_id,
                    :documentcpf,
                    :documentcrmv,
                    :email,
                    :senha,
                    :telefone,
                    :data_nascimento,
                    :endereco_rua,
                    :endereco_numero,
                    :endereco_complemento,
                    :endereco_bairro,
                    :endereco_cidade,
                    :endereco_uf,
                    :endereco_cep,
                    :created_at,
                    :updated_at,
                    :deleted_at
                )";

                $params = [
                    ":uuid" => $usuarioDTO->getUuid(),
                    ":nome_completo" => $usuarioDTO->getNomeCompleto(),
                    ":status_id" => $usuarioDTO->getStatusId(),
                    ":documentcpf" => $usuarioDTO->getDocumentCpf(),
                    ":documentcrmv" => $usuarioDTO->getDocumentCrmv(),
                    ":email" => $usuarioDTO->getEmail(),
                    ":senha" => $usuarioDTO->getSenha(),
                    ":telefone" => $usuarioDTO->getTelefone(),
                    ":data_nascimento" => $usuarioDTO->getDataNascimento(),
                    ":endereco_rua" => $usuarioDTO->getEnderecoRua(),
                    ":endereco_numero" => $usuarioDTO->getEnderecoNumero(),
                    ":endereco_complemento" => $usuarioDTO->getEnderecoComplemento(),
                    ":endereco_bairro" => $usuarioDTO->getEnderecoBairro(),
                    ":endereco_cidade" => $usuarioDTO->getEnderecoCidade(),
                    ":endereco_uf" => $usuarioDTO->getEnderecoUf(),
                    ":endereco_cep" => $usuarioDTO->getEnderecoCep(),
                    ":created_at" => $usuarioDTO->getCreatedAt(),
                    ":updated_at" => $usuarioDTO->getUpdatedAt(),
                    ":deleted_at" => $usuarioDTO->getDeletedAt()
                ];

                // Executa a query de inserção
                DB::execute(sql: $sql, params: $params);

                return [
                    'response_code' => 201,
                    'status' => 'success',
                    'message' => 'Usuário registrado com sucesso.',
                    'output' => [
                        'data' => [
                            'user_id' => DB::lastInsertId(),
                            'uuid' => $usuarioDTO->getUuid()
                        ]
                    ]
                ];
            } catch (Exception $e) {
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Ocorreu um erro ao registrar o usuário. Tente novamente mais tarde.',
                    'output' => ['errors' => $e->getMessage()]
                ];
            }
        }

        /**
         * Busca um único usuário ativo no banco de dados por um identificador específico.
         *
         * Este método recupera todos os detalhes de um usuário na tabela `{$this->usuarioTable}`. A busca é flexível e permite usar qualquer coluna como identificador (`$identifier`), mas é estritamente restrita a registros que não foram logicamente excluídos (`deleted_at IS NULL`).
         *
         * @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'email', 'documentcpf').
         * @param mixed $value O valor correspondente ao identificador que está sendo buscado.
         * @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do usuário, se encontrado.
         */
        public function getUsuarioByIdentifier(string $identifier, mixed $value): array {
            try {
                $sql =
                "SELECT
                    id,
                    uuid,
                    nome_completo,
                    status_id,
                    documentcpf,
                    documentcrmv,
                    email,
                    senha,
                    telefone,
                    data_nascimento,
                    endereco_rua,
                    endereco_numero,
                    endereco_complemento,
                    endereco_bairro,
                    endereco_cidade,
                    endereco_uf,
                    endereco_cep,
                    created_at,
                    updated_at,
                    deleted_at
                FROM {$this->usuarioTable}
                WHERE {$identifier} = :value
                AND deleted_at IS NULL";

                $params = [
                    ':value' => $value
                ];

                $result = DB::fetchOne(sql: $sql, params: $params);

                if (!$result) {
                    return [
                        'response_code' => 404,
                        'status' => 'fail',
                        'message' => 'Usuário não encontrado.',
                        'output' => [
                            'data' => []
                        ]
                    ];
                }

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'output' => ['data' => $result]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Ocorreu um erro ao buscar usuário. Tente novamente mais tarde.',
                ];
            }
        }

        /**
         * Armazena um novo token de autenticação no banco de dados para um usuário específico.
         *
         * Este método é responsável por registrar um token na tabela `{$this->usuarioTokenTable}`. Ele associa o token a um ID de usuário, define o status inicial como não revogado (`revoked = 0`) e registra o timestamp de criação.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** O método inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * 2.  **Execução da Query:** Prepara e executa a instrução `INSERT` com o ID do usuário, a string do token, o status inicial de revogação e o timestamp atual.
         * 3.  **Confirmação:** Se a inserção for bem-sucedida, a transação é confirmada (`DB::commit()`).
         * 4.  **Retorno de Sucesso:** Retorna um array com `status` 'success', o código `201 Created` e uma mensagem de êxito.
         *
         * #### 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 `500 Internal Server Error` e uma mensagem genérica de erro.
         *
         * @param int $user_id O ID do usuário ao qual o token pertence.
         * @param string $token A string do token de autenticação (JWT ou similar).
         * @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem.
         */
        public function storeToken(int $user_id, string $token): array {
            try {
                $sql =
                "INSERT INTO {$this->usuarioTokenTable} (
                    usuario_id,
                    token,
                    revoked,
                    created_at
                ) VALUES (
                    :usuario_id,
                    :token,
                    :revoked,
                    :created_at
                )";

                $params = [
                    ":usuario_id" => $user_id,
                    ":token" => $token,
                    ":revoked" => 0,
                    ":created_at" => (new DateTime())->format(format: 'Y-m-d H:i:s')
                ];

                DB::execute(sql: $sql, params: $params);

                return [
                    'response_code' => 201,
                    'status' => 'success',
                    'message' => 'Token registrado com sucesso.'
                ];
            } catch (Exception $e) {
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Erro ao registrar token. Tente novamente mais tarde.'
                ];
            }
        }

        /**
         * Revoga todos os tokens de autenticação ativos de um usuário específico.
         *
         * Este método atualiza a coluna `revoked` para `1` (revogado) para todos os tokens não revogados (`revoked = 0`) associados a um `user_id` específico na tabela `{$this->usuarioTokenTable}`. Isso invalida todas as sessões ativas do usuário, forçando-o a fazer login novamente.
         *
         * #### Fluxo de Operação:
         * 1.  **Execução da Query:** Prepara e executa a instrução `UPDATE` para revogar os tokens.
         * 2.  **Confirmação:** Se a atualização for bem-sucedida, retorna um array com `status` 'success' e código `200 OK`.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` capturada durante o processo reverte qualquer transação de banco de dados e retorna um array com `status` 'error' e código `500 Internal Server Error`, com uma mensagem de erro genérica.
         *
         * @param int $user_id O ID do usuário para o qual os tokens serão revogados.
         * @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem descritiva.
         */
        public function revokeOldTokens(int $user_id): array {
            try {
                $sql = 
                "UPDATE {$this->usuarioTokenTable} SET
                    revoked = 1,
                    updated_at = CURRENT_TIMESTAMP
                WHERE usuario_id = :usuario_id
                AND revoked = 0";

                $params = [
                    ":usuario_id" => $user_id
                ];

                DB::execute(sql: $sql, params: $params);

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Old tokens revoked successfully.'
                ];
            } catch (Exception $e) {
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Error revoking old tokens. Please try again later.'
                ];
            }
        }

        /**
         * Busca um token de usuário no banco de dados por um identificador específico.
         *
         * Este método recupera os dados de um único token de autenticação da tabela `{$this->usuarioTokenTable}` de forma flexível, permitindo a busca por qualquer coluna (`$identifier`) e o valor correspondente.
         *
         * #### Fluxo de Operação:
         * 1.  **Execução da Query**: Monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado (`:value`) para o valor.
         * 2.  **Verificação de Resultados**:
         * - Se o token for encontrado, retorna um array com **`status` 'success'**, código **200 OK** e os dados do token.
         * - Se o token **não for encontrado**, retorna um array com **`status` 'fail'** e código **404 Not Found**.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'** e código **500 Internal Server Error**, com uma mensagem genérica de erro.
         *
         * @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'token').
         * @param mixed $value O valor correspondente ao identificador que está sendo buscado.
         * @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se encontrado.
         */
        public function getTokenByidentifier(string $identifier, mixed $value): array {
            try {
                $sql =
                "SELECT
                    id,
                    usuario_id,
                    token,
                    revoked,
                    created_at,
                    updated_at
                FROM {$this->usuarioTokenTable}
                WHERE {$identifier} = :value";

                $params = [
                    ':value' => $value
                ];

                $result = DB::fetchOne(sql: $sql, params: $params);

                if (!$result) {
                    return [
                        'response_code' => 404,
                        'status' => 'fail',
                        'message' => 'Token não encontrado.',
                        'output' => [
                            'data' => []
                        ]
                    ];
                }

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'output' => ['data' => $result]
                ];
            } catch(Exception $e) {
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Ocorreu um erro ao buscar token. Tente novamente mais tarde.',
                ];
            }
        }

        /**
         * Revoga um token de autenticação de usuário no banco de dados.
         *
         * Este método marca um token específico como revogado, atualizando a coluna `revoked` para `1`. Ele garante que apenas tokens que ainda não foram revogados (`revoked = 0`) sejam atualizados.
         *
         * #### Fluxo de Operação:
         * 1.  **Execução da Query**: Prepara e executa a instrução `UPDATE` para definir `revoked = 1` para o token correspondente.
         * 2.  **Confirmação**: Se a atualização for bem-sucedida, retorna um array com **`status` 'success'** e código **200 OK**.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'** e código **500 Internal Server Error**, com uma mensagem de erro genérica.
         *
         * @param string $token O token de autenticação a ser revogado.
         * @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem.
         */
        public function revokeToken(string $token): array {
            try {
                $sql = 
                "UPDATE {$this->usuarioTokenTable} SET
                    revoked = 1,
                    updated_at = CURRENT_TIMESTAMP
                WHERE token = :token
                AND revoked = 0";

                $params = [
                    ":token" => $token
                ];

                DB::execute(sql: $sql, params: $params);

                return [
                    'response_code' => 200,
                    'status' => 'success',
                    'message' => 'Token revoked successfully.'
                ];
            } catch (Exception $e) {
                return [
                    'response_code' => 500,
                    'status' => 'error',
                    'message' => 'Error revoking token. Please try again later.'
                ];
            }
        }
    }