<?php

namespace App\Services;

use App\Enums\StatusAgendamentoAtendimentoEnum;
use App\Enums\TipoAgendamentoEnum;
use App\Exceptions\AgendamentoAbertoPorOutroFamiliarException;
use App\Exceptions\FilhoMaiorDeIdadeException;
use App\Exceptions\HorarioIndisponivelFeriadoException;
use App\Exceptions\HorarioIndisponivelForaHorarioPrestadorException;
use App\Exceptions\HorarioIndisponivelSobrepoeHorarioOdontologicoException;
use App\Exceptions\PrestadorSemEspecialidadeException;
use App\Exceptions\PrestadorSemHorariosDefinidosException;
use App\Exceptions\TipoPrestadorErradoAtendimentoException;
use App\Models\AgendamentoAtendimento;
use App\Models\Dependente;
use App\Models\Feriado;
use App\Models\PrestadorServico;
use Carbon\Carbon;

class AgendaAtendimentoOdontologicoService extends AgendaAtendimentoService
{
    protected $prestador;
    protected $emTratamento;
    protected $permiteAgendamentoProximo;

    public function __construct(Carbon $inicio, Carbon $fim, PrestadorServico $prestador, $pessoa, $emergencia, $emTratamento, $permiteAgendamentoProximo = false)
    {
        parent::__construct($inicio, $fim, $pessoa, TipoAgendamentoEnum::odontologico());

        $this->prestador = $prestador;
        $this->emergencia = $emergencia;
        $this->emTratamento = $emTratamento;
        $this->servicoAtendimento = null;
        $this->permiteAgendamentoProximo = $permiteAgendamentoProximo;
    }

    public function agenda()
    {
        $agendamento = new AgendamentoAtendimento();
        $agendamento->inicio = $this->inicio;
        $agendamento->fim = $this->fim;
        $agendamento->prestadorServico()->associate($this->prestador);
        $agendamento->agendavel()->associate($this->pessoa);
        $agendamento->tipo = strtolower($this->tipo->getName());
        $agendamento->status = StatusAgendamentoAtendimentoEnum::aberto();
        $agendamento->emergencia = $this->emergencia;
        $agendamento->em_tratamento = $this->emTratamento;

        $this->verificaDisponibilidade();
        $this->verificaSeHaAgendamentoProximo();

        if (!$this->prestador->tipo->isDentista()) {
            throw new TipoPrestadorErradoAtendimentoException($this->prestador, $this->tipo);
        }

        if (!$this->prestador->especialidade) {
            throw new PrestadorSemEspecialidadeException($this->prestador);
        }

        // Filho maior de idade só pode ser atendido caso esteja em tratamento
        // ou seja especial
        if ($this->pessoa instanceof Dependente && $this->pessoa->grau_parentesco->isFilho()
                                                && $this->pessoa->maiorDeIdade()
                                                && ! $this->pessoa->especial) {
            if (! $this->emTratamento) {
                throw new FilhoMaiorDeIdadeException();
            }

            $agendamento->em_tratamento = true;
        }

        // Não permite agendamento quando já há um agendamento para um membro da família,
        // quando os agendamentos são para a mesma especialidade
        if ($this->pessoa instanceof Dependente && $this->prestador->tipo->isDentista()) {
            $dependivel = $this->pessoa->dependivel; // De quem ele depende (associado, funcionário)
            $outrosDependentes = $dependivel->dependentes;

            $familia = collect([$dependivel])->push($outrosDependentes)->flatten();
            $familia->each(function ($familiar) use ($agendamento) {
                $familiarTemAgendamento = $familiar->agendamentos()
                                                   ->deEspecialidade($this->prestador->especialidade)
                                                   ->emAberto()->get()->isNotEmpty();
                if ($familiarTemAgendamento) {
                    if (! $this->emergencia && ! $this->emTratamento) {
                        throw new AgendamentoAbertoPorOutroFamiliarException();
                    }

                    $agendamento->emergencia = true;
                }
            });
        }
        
        $agendamento->save();
        
        return $agendamento;
    }

    public function verificaDisponibilidade()
    {
        if (Feriado::eFeriado($this->inicio, $this->fim)) {
            throw new HorarioIndisponivelFeriadoException($this->inicio, $this->fim);
        }
        
        if (! $this->dentroHorarioPrestador()) {
            throw new HorarioIndisponivelForaHorarioPrestadorException($this->inicio, $this->fim);
        }

        if ($this->sobrepoeHorario()) {
            $agendamentoSobreposto = $this->agendamentoSobreposto();
            if (! $this->emergencia) {
                throw new HorarioIndisponivelSobrepoeHorarioOdontologicoException($this->inicio, $this->fim, $agendamentoSobreposto);
            }
        }
    }

    public function dentroHorarioPrestador()
    {
        $horarios = $this->prestador->horariosTrabalho;

        if ($horarios->isEmpty()) {
            throw new PrestadorSemHorariosDefinidosException($this->prestador);
        }

        $diaSemana = $this->diaSemana($this->inicio);

        return $this->prestador->horariosTrabalho()
            ->where('dia_semana', 'ilike', $diaSemana->getName())
            ->where('inicio', '<=', $this->inicio)
            ->where('fim', '>=', $this->fim)
            ->get()
            ->isNotEmpty();
    }
}
