<?php

namespace App\DAO;

use App\DAO\DAOResponse\DAOResponse;
use App\DAO\DAOResponse\DAOResponseInterface;
use App\DAO\Order\QueryOrder;
use App\DAO\Pagination\Pagination;
use App\Entities\Agenda\Agenda;
use App\Mappers\AgendaMapper;
use App\Models\AgendaModel;
use App\Services\JWT\UsuarioLogado;
use App\Services\JWTService;
use App\Services\LogService;
use App\Utils\Collections\GeneralCollection;
use App\Utils\Collections\QueryFilterCollection;
use App\Utils\Collections\QueryOrderCollection;
use App\ViewModels\Agenda\AgendaMultipleRegistrationViewModel\AgendaMultipleRegistrationViewModel;
use App\ViewModels\Agenda\AgendaRegistrationViewModel\AgendaRegistrationViewModel;
use App\ViewModels\Agenda\AgendaUpdateViewModel\AgendaUpdateViewModel;
use DateTime;

class AgendaDAO extends DAO {

	public function deleteAgenda(int $id_agenda) {

		$agenda_model = new AgendaModel();
		$dao_response = $this->getDAOResponseForDeletion( $agenda_model, $id_agenda );
		return $dao_response;

	}

	public function persistAgendaRegistrationVM(AgendaRegistrationViewModel $agenda_registration_vm): DAOResponse {

		$dao_response = new DAOResponse();
		$agenda = $this->persistAgenda( $agenda_registration_vm );
		$error = $this->db->error();
		if ($error && $error ['code']) {
			$status_persistence = $error ['code'];
			$dao_response_errors = [ ];
			if ($status_persistence === DAOResponseInterface::FOREIGN_KEY_DONT_EXISTS) {
				if (strpos( $error ['message'], 'fk_agenda_id_medico' ) !== false) {
					$dao_response_errors ['id_medico'] = 'Esse médico não existe.';
				}
				if (strpos( $error ['message'], 'fk_agenda_id_tipo_procedimento' ) !== false) {
					$dao_response_errors ['procedimento'] = 'Esse procedimento não existe.';
				}
				if (strpos( $error ['message'], 'fk_agenda_adicionado_por' ) !== false) {
					$dao_response_errors ['adicionado_por'] = 'Esse usuário não existe.';
				}
                if (strpos( $error ['message'], 'agenda_ibfk_1' ) !== false) {
                    $dao_response_errors ['unidade'] = 'Essa unidade não existe.';
                }
			} else if ($status_persistence === DAOResponseInterface::DUPLICATED_UNIQUE_KEY) {
				if (strpos( $error ['message'], 'AGENDA.AGENDA_codigo_unique_key' ) !== false) {
					$dao_response_errors ['hora_inicio'] = 'Esse horário já está ocupado.';
				}
			}
			$dao_response->setErrors( $dao_response_errors );
			log_message( 'error', json_encode( $error ) );
		} else {
			$status_persistence = ! ! $agenda->getId() ? DAOResponseInterface::SUCESS : DAOResponseInterface::DATABASE_CONNECTION_ERROR;
		}
		$this->db->close();
		$dao_response->setStatus( $status_persistence );
		if ($status_persistence === DAOResponseInterface::SUCESS) {
			$dao_response->setData( $agenda );
		}
		return $dao_response;

	}

	private function persistAgenda(AgendaRegistrationViewModel $agenda_registration_vm): Agenda {

		$agenda_model = new AgendaModel();
		$agenda = AgendaMapper::getAgendaFromAgendaRegistrationVM( $agenda_registration_vm );
		$agenda_model->registerAgenda( $agenda );
		return $agenda;

	}

	public function getAgendaConflicts(AgendaRegistrationViewModel $agenda_registration_vm): DAOResponse {

		$sql = "
            SELECT 
                `id`,
                `data`,
                SUBSTRING(`hora_inicio`, 1, 5) as hora_inicio,
                SUBSTRING(`hora_final`, 1, 5) as hora_final
            FROM `agenda`
            WHERE
                :data: = data AND 
                :id_medico: = id_medico AND
                (
                    (:hora_inicio: >= hora_inicio AND :hora_inicio: <  hora_final) OR
                    (:hora_inicio: >= hora_inicio AND :hora_final:  <  hora_final) OR
                    (:hora_inicio: <  hora_inicio AND :hora_inicio: >= hora_final) OR
                    (:hora_inicio: <  hora_inicio AND :hora_final:  >= hora_final) OR
                    (:hora_final:  >= hora_inicio AND :hora_inicio: <  hora_final) OR
                    (:hora_final:  >= hora_inicio AND :hora_final:  <  hora_final) OR
                    (:hora_final:  <  hora_inicio AND :hora_inicio: >= hora_final) OR
                    (:hora_final:  <  hora_inicio AND :hora_final:  >= hora_final)
                )
            ORDER BY hora_inicio
        ";
		$binds = [
				'data' => $agenda_registration_vm->getData()->format( 'Y-m-d' ),
				'hora_inicio' => $agenda_registration_vm->getHoraInicio()->format( 'H:i' ),
				'hora_final' => $agenda_registration_vm->getHoraFinal()->format( 'H:i' ),
				'id_medico' => $agenda_registration_vm->getIdMedico() ];
		$query = $this->db->query( $sql, $binds );
		return $this->getResponseForSchedulesConflictByQuery( $query );

	}

	public function getAgendaConflictsForMultipleRegistration(AgendaMultipleRegistrationViewModel $agenda_multiple_registration_vm, GeneralCollection $service_dates): DAOResponse {

		$sql = "
            SELECT 
                `id`,
                `data`,
                SUBSTRING(`hora_inicio`, 1, 5) as hora_inicio,
                SUBSTRING(`hora_final`, 1, 5) as hora_final
            FROM `agenda`
            WHERE
                `data` in :data_list: AND 
                 id_medico = :id_medico: AND
                hora_final > :hora_inicio: AND 
                hora_inicio < :hora_final:
            ORDER BY `data`, hora_inicio
        ";
		$list_dates = $this->getConflictsListDates( $service_dates );
		$sql = str_replace( ':data_list:', $list_dates, $sql );
		$sql = trim( preg_replace( '/\s{2,}/', ' ', $sql ) );
		$binds = [
				'id_medico' => $agenda_multiple_registration_vm->getIdMedico(),
				'hora_inicio' => $agenda_multiple_registration_vm->getHoraInicio()->format( 'H:i' ),
				'hora_final' => $agenda_multiple_registration_vm->getHoraFim()->format( 'H:i' ) ];
		$query = $this->db->query( $sql, $binds );
		return $this->getResponseForSchedulesConflictByQuery( $query );

	}

	// Método usado tanto na obtenção de conflitos para horários individuais quanto para o cadastro múltiplo.
	private function getResponseForSchedulesConflictByQuery($query): DAOResponse {

		$result_array = $query->getResultArray();
		$error = $this->db->error();
		$dao_response = new DAOResponse();
		if ($error && $error ['code']) {
			$dao_response->setStatus( DAOResponseInterface::DATABASE_CONNECTION_ERROR );
		} else {
			if (count( $result_array )) {
				$dao_response->setStatus( DAOResponseInterface::SUCESS );
				$agenda_conflictig_vm_collection = AgendaMapper::getAgendaConflictingVMCollectionFromArray( $result_array );
				$dao_response->setData( $agenda_conflictig_vm_collection );
			} else {
				$dao_response->setStatus( DAOResponseInterface::RECORD_NOT_FOUND );
			}
		}
		return $dao_response;

	}

	private function getConflictsListDates(GeneralCollection $service_dates) {

		$mapper = function (DateTime $date) {
			return '\'' . $date->format( 'Y-m-d' ) . '\'';
		};
		$filter = $service_dates->map( $mapper )->join( ',' );
		return "($filter)";

	}

	public function getAgendaList(QueryFilterCollection $query_filter_collection, QueryOrderCollection $query_order_collection, ?Pagination $pagination): DAOResponse {

		$sql = <<<SQL
		    SELECT
		        agenda.id AS id,
		        agenda.`data` as data,
		        agenda.id_medico as id_medico,
		        agenda.id_unidade as id_unidade,
		        SUBSTRING(agenda.hora_inicio, 1, 5) as hora_inicio,
		        SUBSTRING(agenda.hora_final, 1, 5) as hora_final,
		        pessoa_fisica.nome as nome_paciente,
		        agenda.id_tipo_procedimento as id_tipo_procedimento,
		        agendamento.id as id_agendamento,
		        agendamento.id_paciente as id_paciente,
		        agendamento.id_paciente as id_cliente,
		        procedimento.id AS id_procedimento,
		        IF(consulta.id IS NULL, 0, consulta.id) as id_consulta,
		        tipo_procedimento.descricao as tipo_procedimento,
		        procedimento.descricao as procedimento,
		        agendamento.cancelado_em as cancelado_em,
		        pessoa_fisica.nome as nome_cliente,
		        pet.numero_chip as nome_pet
		    FROM agenda
		    INNER JOIN tipo_procedimento ON tipo_procedimento.id = agenda.id_tipo_procedimento
		    LEFT JOIN agendamento ON agenda.id = agendamento.id_agenda
		    LEFT JOIN procedimento ON agendamento.id_procedimento = procedimento.id
		    LEFT JOIN consulta ON consulta.id_agendamento = agendamento.id
		    LEFT JOIN paciente ON agendamento.id_paciente = paciente.id
		    LEFT JOIN pessoa ON paciente.id_pessoa = pessoa.id
		    LEFT JOIN usuario ON pessoa.id = usuario.id_pessoa
		    LEFT JOIN pessoa_fisica ON pessoa_fisica.id_pessoa = pessoa.id
		    LEFT JOIN cliente ON cliente.id_pessoa = pessoa.id 
		    LEFT JOIN pet_cliente ON pet_cliente.id_cliente = cliente.id 
		    LEFT JOIN pet ON pet.id = pet_cliente.id_pet
			WHERE 1=1 
		SQL;
		$jwt_payload = JWTService::getTokenJWTPayload();
		if ($jwt_payload->getProfile() === PERFIL_PACIENTE) {
			$sql .= "
			        AND (usuario.`id` = {$jwt_payload->getUid()} OR usuario.`id` IS NULL)
			";
		}
		if ($jwt_payload->getProfile() === PERFIL_ASSISTENTE) {
			$sql .= "
			        AND (agendamento.`adicionado_por` = {$jwt_payload->getUid()} OR agendamento.`adicionado_por` IS NULL)
			";
		}
		if ($jwt_payload->getProfile() === PERFIL_MEDICO) {
			$query = db_connect()->query("
				SELECT
					m.id as medico_id
				FROM usuario u
				inner join pessoa p on p.id = u.id_pessoa 
				inner join profissional p2 on p2.id_pessoa  = p.id
				inner join medico m on m.id_profissional = p2.id
				where
					u.id = :usuario_id:
				limit 1
			", ['usuario_id' => UsuarioLogado::getUsuarioId()])->getResultArray();
			$medicoId = count($query) ? (int) $query[0]['medico_id'] : 0;
			$sql .= " AND (agenda.id_medico = $medicoId OR agenda.id_medico is null)";
		}
		$query_order_collection->push( QueryOrder::builder()->setTable( 'agendamento' )->setNameParam( 'cancelado_em' )->setDesc( false )->build() );
		return $this->getDAOResponseFromPaginatedSelect( [ ], $pagination, $query_filter_collection, $query_order_collection, $sql, function (array $result_array) {
			return AgendaMapper::getAgendaListingVMCollectionFromArray( $result_array );
		} );

	}

	public function persistAgendaCollection(GeneralCollection $agenda_collection): DAOResponse {

		$agenda_model = new AgendaModel();
		$this->db->transStart();
		$insert_status = $agenda_model->registerAgendaCollection( $agenda_collection );
		$error = $this->db->error();
		$dao_response = new DAOResponse();
		if ($error && $error ['code']) {
			$dao_response->setStatus( $error ['code'] );
			$dao_response->setErrors( $this->agendaInsertErrors( $error ) );
		} else {
			if ($insert_status) {
				$this->db->transComplete();
				$dao_response->setStatus( DAOResponseInterface::SUCESS );
			} else {
				$dao_response->setStatus( DAOResponseInterface::DATABASE_CONNECTION_ERROR );
			}
		}
		return $dao_response;

	}

	private function agendaInsertErrors(array $error): array {

		$dao_response_errors = [ ];
		if ($error ['code'] === DAOResponseInterface::FOREIGN_KEY_DONT_EXISTS) {
			if (strpos( $error ['message'], 'fk_agenda_id_medico' ) !== false) {
				$dao_response_errors ['id_medico'] = 'Esse médico não existe.';
			}
			if (strpos( $error ['message'], 'fk_agenda_id_tipo_procedimento' ) !== false) {
				$dao_response_errors ['procedimento'] = 'Esse tipo de procedimento não existe.';
			}
			if (strpos( $error ['message'], 'fk_agenda_adicionado_por' ) !== false) {
				$dao_response_errors ['adicionado_por'] = 'Esse usuário não existe.';
			}
            if (strpos( $error ['message'], 'agenda_ibfk_1' ) !== false) {
                $dao_response_errors ['unidade'] = 'Essa unidade não existe.';
            }
		} else if ($error ['code'] === DAOResponseInterface::DUPLICATED_UNIQUE_KEY) {
			if (strpos( $error ['message'], 'AGENDA.AGENDA_codigo_unique_key' ) !== false) {
				$dao_response_errors ['hora_inicio'] = 'Esse horário já está ocupado.';
			}
		}
		return $dao_response_errors;

	}

	public function updateAgenda(int $id_agenda, AgendaUpdateViewModel $agenda_update_vm): DAOResponse {

		$agenda_model = new AgendaModel();
		$agenda = AgendaMapper::getAgendaFromAgendaUpdateVM( $agenda_update_vm );
		$query_result = $agenda_model->updateAgendaByID( $id_agenda, $agenda );
		$error = $this->db->error();
		$this->db->close();
		$dao_response = new DAOResponse();
		if ($error && $error ['code']) {
			$status_response = $error ['code'];
			$dao_response_errors = [ ];
			if ($status_response === DAOResponseInterface::FOREIGN_KEY_DONT_EXISTS) {
				if (strpos( $error ['message'], 'fk_agenda_id_tipo_procedimento' ) !== false) {
					$dao_response_errors ['id_tipo_procedimento'] = 'Esse tipo de procedimento não existe.';
				}
				if (strpos( $error ['message'], 'fk_agenda_alterado_por' ) !== false) {
					$dao_response_errors ['alterado_por'] = 'Esse usuário não existe.';
				}
			}
			$dao_response->setErrors( $dao_response_errors );
			(new LogService())->log_error( $status_response, "Ocorreu um erro durante a atualização da agenda $id_agenda", [
					$agenda->toArray(),
					$error ] );
		} else {
			$status_response = $query_result ? DAOResponseInterface::SUCESS : DAOResponseInterface::RECORD_NOT_FOUND;
		}
		$dao_response->setStatus( $status_response );
		return $dao_response;

	}

	public function getAgendaByID(int $id_agenda): DAOResponse {

		$sql = '
            SELECT
                id,
                codigo,
                id_medico,
                `data`,
                SUBSTRING(hora_inicio, 1, 5) as hora_inicio,
                SUBSTRING(hora_final, 1, 5) as hora_final,
                id_tipo_procedimeno
            FROM
                agenda
            WHERE 
                  id = :id_agenda:
            LIMIT 1
        ';
		$binds = [
				'id_agenda' => $id_agenda ];
		$query_filter_collection = new QueryFilterCollection();
		return $this->getDAOResponseFromUniqueRowSelect( $binds, $query_filter_collection, $sql, function (array $result_array): Agenda {
			return AgendaMapper::getAgendaByArray( $result_array [0] );
		} );

	}

}
