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

    use DateTime;
    use Exception;
    use Ramsey\Uuid\Uuid;
    use Zampet\Module\Auth\v0\Models\UsuarioModel;
    use Zampet\Services\DB;
    use Zampet\Services\JWTService;
    use Zampet\Helpers\DataValidator;
    use Zampet\Module\Tutor\v0\DTO\PetDTO;
    use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
    use Zampet\Module\Tutor\v0\Models\PetModel;

    class PetService {
        /**
         * Retorna a lista de todas as espécies de pets disponíveis no sistema.
         *
         * Este método atua como um **wrapper** (invólucro) para o método de mesmo nome na classe `PetModel`. Ele delega a responsabilidade de buscar a lista completa das espécies de pets cadastradas para a camada de modelo (DAO).
         *
         * @return array Um array associativo contendo o status da operação, o código HTTP e os dados das espécies, conforme retornado pelo `PetModel`.
         */
        public function getAllEspecies(): array {
            return (new PetModel())->getAllEspecies();
        }

        /**
         * Lista todos os pets de um tutor específico, buscando pelo UUID do tutor.
         *
         * Este método atua como um **wrapper** para o método `listPetsByTutorIdentifier` da classe `PetModel`. Ele delega a responsabilidade de buscar a lista completa e detalhada de pets associados a um tutor, utilizando o UUID como o identificador principal.
         *
         * @param string $uuid O UUID do tutor cujos pets devem ser listados.
         * @return array Um array associativo contendo o status da operação, o código HTTP e os dados dos pets, conforme retornado pelo `PetModel`.
         */
        public function listPetsByTutorUuid(string $uuid): array {
            return (new PetModel())->listPetsByTutorIdentifier(identifier: 'uuid', value: $uuid);
        }

        /**
         * Busca uma espécie de pet no sistema por um identificador específico.
         *
         * Este método atua como um **wrapper** para o método de busca de espécies na classe `PetModel`. Ele delega a responsabilidade de consultar a espécie para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados.
         *
         * @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'uuid', 'descricao').
         * @param string $value O valor correspondente ao identificador que está sendo buscado.
         * @return array Um array associativo contendo o status da operação, o código HTTP e os dados da espécie, conforme retornado pelo `PetModel`.
         */
        public function getEspecieByIdentifier(string $identifier, string $value): array {
            return (new PetModel())->getEspecieByIdentifier(identifier: $identifier, value: $value);
        }

        /**
         * Busca uma raça de pet no sistema por um identificador específico.
         *
         * Este método atua como um **wrapper** (invólucro) para o método de busca de raças na classe `PetModel`. Ele delega a responsabilidade de consultar a raça para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados e retorna o resultado.
         *
         * @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'descricao').
         * @param string $value O valor correspondente ao identificador que está sendo buscado.
         * @return array Um array associativo contendo o status da operação, o código HTTP e os dados da raça, conforme retornado pelo `PetModel`.
         */
        public function getRacaByIdentifier(string $identifier, string $value): array {
            return (new PetModel())->getRacaByIdentifier(identifier: $identifier, value: $value);
        }

        /**
         * Busca um pet no sistema por um identificador específico.
         *
         * Este método atua como um **wrapper** (invólucro) para o método de busca de pets na classe `PetModel`. Ele delega a responsabilidade de consultar o pet para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados.
         *
         * @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'nome').
         * @param string $value O valor correspondente ao identificador que está sendo buscado.
         * @return array Um array associativo contendo o status da operação, o código HTTP e os dados do pet, conforme retornado pelo `PetModel`.
         */
        public function getPetByIdentifier(string $identifier, string $value): array {
            return (new PetModel())->getPetByIdentifier(identifier: $identifier, value: $value);
        }

        /**
         * Cria uma nova raça de pet no sistema após validação.
         *
         * Este método orquestra a criação de uma nova raça de pet. Ele realiza a validação dos dados de entrada, define metadados essenciais (UUID) e persiste o registro no banco de dados, garantindo a atomicidade da operação através de uma transação.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * 2.  **Validação de Dados:** Utiliza o `DataValidator` para verificar se os campos **'especie_id'** e **'descricao'** foram preenchidos.
         * 3.  **Encerramento em Caso de Falha na Validação:** Se houver erros de validação, a transação é revertida (`DB::rollBack()`) e um array de erro formatado é retornado com o código `400 Bad Request`.
         * 4.  **Preparação Final dos Dados:** Define um **UUID** único para a nova raça.
         * 5.  **Persistência:** Chama o método `storeRaca` do `PetModel` para inserir o registro no banco de dados. Se a persistência falhar, uma `Exception` é lançada com a mensagem de erro do modelo.
         * 6.  **Confirmação e Retorno:** Se a persistência for bem-sucedida, a transação é **confirmada** (`DB::commit()`) e o resultado de sucesso é retornado com o código `201 Created`.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada durante o processo é capturada, e a transação é **revertida** (`DB::rollBack()`).
         * - Retorna um array de erro formatado contendo o código HTTP e a mensagem da exceção.
         *
         * @param PetRacaDTO $petRacaDTO O objeto DTO contendo o ID da espécie e a descrição da nova raça.
         * @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados de saída, ou erros de validação.
         */
        public function storeRaca(PetRacaDTO $petRacaDTO): array {
            // Inicia transação no banco de dados
            DB::beginTransaction();

            try {
                // Valida os dados do DTO
                $dataValidador = new DataValidator(inputs: $petRacaDTO->toArray());
                $dataValidador->validate(field: 'especie_id', rule: 'notEmpty', message: 'Espécie é obrigatória.');
                $dataValidador->validate(field: 'descricao', rule: 'notEmpty', message: 'Descrição é obrigatória.');

                // 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()
                        ]
                    ];
                }

                // Define dados adicionais
                $petRacaDTO->setUuid(uuid: Uuid::uuid7()->toString());

                // Cria a nova raça no banco de dados
                $storeRaca = (new PetModel())->storeRaca(petRacaDTO: $petRacaDTO);
                if($storeRaca['status'] !== 'success') {
                    throw new Exception(message: $storeRaca['message'], code: $storeRaca['response_code']);
                }

                DB::commit();
                return [
                    'response_code' => 201,
                    'status' => 'success',
                    'message' => 'Raça criada com sucesso.',
                    'output' => $storeRaca['output']
                ];
            } catch(Exception $e) {
                DB::rollBack();
                return [
                    'response_code' => $e->getCode(),
                    'status' => 'error',
                    'message' => $e->getMessage()
                ];
            }
        }

        /**
         * Armazena um novo pet no sistema e o associa ao usuário autenticado.
         *
         * Este método é responsável por orquestrar a criação de um novo pet, desde a validação inicial até a persistência do pet e o registro da relação com o tutor. A operação é **atômica** e garante a integridade dos dados através de uma transação de banco de dados.
         *
         * #### Fluxo de Operação:
         * 1.  **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
         * 2.  **Validação de Dados:** O `DataValidator` verifica a presença e validade dos campos obrigatórios (nome, raça UUID, data de nascimento e sexo). Se falhar, a transação é revertida e um erro `400 Bad Request` é retornado.
         * 3.  **Validação de Duplicidade (RGA):** Se fornecido, o Registro Geral Animal (RGA) é verificado quanto à unicidade. Se um pet com o mesmo RGA for encontrado, uma `Exception` é lançada com o código `409 Conflict`.
         * 4.  **Preparação Final dos Dados:** O UUID do pet e o timestamp de criação são definidos.
         * 5.  **Criação do Pet:** O método `storePet` do `PetModel` é chamado para inserir o registro do pet. Se falhar, uma `Exception` é lançada.
         * 6.  **Associação ao Tutor:** O token JWT é decodificado para obter o UUID do tutor. O ID interno do tutor é buscado, e o pet é associado ao tutor através do método `storeUsuarioPet`. Se o tutor não for encontrado ou a associação falhar, uma `Exception` é lançada.
         * 7.  **Confirmação e Retorno:** Se todas as operações forem bem-sucedidas, a transação é **confirmada** (`DB::commit()`) e o resultado de sucesso é retornado com o código `201 Created`.
         *
         * #### Tratamento de Erros:
         * - Qualquer `Exception` lançada durante o processo é capturada, a transação é **revertida** (`DB::rollBack()`), e um array de erro formatado contendo o código HTTP e a mensagem da exceção é retornado.
         *
         * @param PetDTO $petDTO O objeto DTO contendo os dados do novo pet.
         * @return array Um array associativo com o status da operação, código HTTP e os dados do pet criado.
         */
        public function storePet(PetDTO $petDTO): array {
            // Inicia transação no banco de dados
            DB::beginTransaction();

            try {
                // Valida os dados do DTO
                $dataValidador = new DataValidator(inputs: $petDTO->toArray());
                $dataValidador->validate(field: 'nome', rule: 'notEmpty', message: 'Nome do pet é obrigatório.');
                $dataValidador->validate(field: 'sexo', rule: 'notEmpty', message: 'Sexo do pet é obrigatório.');
                $dataValidador->validate(field: 'raca_id', rule: 'notEmpty', message: 'Raça do pet é obrigatória.');
                $dataValidador->validate(field: 'especie_id', rule: 'notEmpty', message: 'Espécie do pet é obrigatória.');
                $dataValidador->validate(field: 'data_nascimento', rule: 'notEmpty', message: 'Data de nascimento do pet é obrigatória.');

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

                // Verifica se o Registro Geral Animal do PET (RGA) é único, se fornecido
                if(($petDTO->getRegistroGeralAnimal() !== null) && !empty($petDTO->getRegistroGeralAnimal())) {
                    $existingPet = $this->getPetByIdentifier(identifier: 'registro_geral_animal', value: $petDTO->getRegistroGeralAnimal());
                    if($existingPet['status'] === 'success' && !empty($existingPet['output']['data'])) {
                        throw new Exception(message: 'Já existe um pet cadastrado com este Registro Geral Animal (RGA).', code: 409);
                    }
                }

                // Define dados adicionais
                $petDTO->setUuid(uuid: Uuid::uuid7()->toString());
                $petDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));

                // Cria o novo pet no banco de dados
                $storePet = (new PetModel())->storePet(petDTO: $petDTO);
                if($storePet['status'] !== 'success') {
                    throw new Exception(message: $storePet['message'], code: $storePet['response_code']);
                }

                // Consulta o token JWT
                $decodedToken = JWTService::decode(token: JWTService::getBearerToken());

                // Consulta o ID do tutor pelo UUID
                $getTutor = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'uuid', value: $decodedToken['user_uuid']);
                if($getTutor['status'] !== 'success' || empty($getTutor['output']['data'])) {
                    throw new Exception(message: 'Tutor não encontrado.', code: 404);
                }
                
                // Relaciona o pet ao tutor autenticado
                $dataValidador = (new PetModel())->storeUsuarioPet(usuario_id: $getTutor['output']['data']['id'], pet_id: $storePet['output']['data']['id']);
                if($dataValidador['status'] !== 'success') {
                    throw new Exception(message: $dataValidador['message'], code: $dataValidador['response_code']);
                }

                DB::commit();
                return [
                    'response_code' => 201,
                    'status' => 'success',
                    'message' => 'Pet criado com sucesso.',
                    'output' => $storePet['output']
                ];
            } catch(Exception $e) {
                DB::rollBack();
                return [
                    'response_code' => $e->getCode(),
                    'status' => 'error',
                    'message' => $e->getMessage()
                ];
            }
        }
    }