<?php

namespace Tests\Unit;

use App\Enums\DiaSemanaEnum;
use App\Enums\EspecialidadeOdontologiaEnum;
use App\Enums\GrauParentescoEnum;
use App\Enums\TipoAgendamentoEnum;
use App\Enums\TipoPrestadorServicoEnum;
use App\Exceptions\AgendamentoAbertoPorOutroFamiliarException;
use App\Exceptions\FilhoMaiorDeIdadeException;
use App\Exceptions\HorarioIndisponivelForaHorarioPrestadorException;
use App\Exceptions\HorarioIndisponivelSobrepoeHorarioOdontologicoException;
use App\Exceptions\PrestadorSemEspecialidadeException;
use App\Exceptions\PrestadorSemHorariosDefinidosException;
use App\Exceptions\TipoPrestadorErradoAtendimentoException;
use App\Exceptions\UltimoAtendimentoMuitoProximoException;
use App\Models\AgendamentoAtendimento;
use App\Models\Dependente;
use App\Models\HorarioTrabalho;
use App\Models\PrestadorServico;
use App\Models\Servidor;
use App\Services\AgendaAtendimentoOdontologicoService;
use Carbon\Carbon;
use ConfiguracaoSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase as TestCase;

class AgendaAtendimentoOdontologicoServiceTest extends TestCase
{
    use RefreshDatabase;

    public function setUp(): void
    {
        parent::setUp();

        $this->seed(ConfiguracaoSeeder::class);
    }

    public function test_agenda_prestador_sem_especialidade()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');
        $pessoa = factory(Dependente::class)->create();
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $pessoa, false, false);
        $this->expectException(PrestadorSemEspecialidadeException::class);
        $service->agenda();
    }

    public function test_prestador_nao_dentista_nao_pode_receber_atendimento()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');
        $pessoa = factory(Dependente::class)->create();
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::advogado(),
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $pessoa, false, false);
        $this->expectException(TipoPrestadorErradoAtendimentoException::class);
        $service->agenda();
    }

    public function test_agenda_filho_maior_18_anos_nao_pode_agendar()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $filhoMaior18Anos = factory(Dependente::class)->create([
            'data_nascimento' => Carbon::today()->subYear(18)->format('d/m/Y')
        ]);
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $filhoMaior18Anos, false, false);

        $this->expectException(FilhoMaiorDeIdadeException::class);
        $service->agenda();
    }

    public function test_agenda_filho_maior_18_anos_pode_agendar_se_em_tratamento()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $filhoMaior18Anos = factory(Dependente::class)->create([
            'data_nascimento' => Carbon::today()->subYear(18)->format('d/m/Y')
        ]);
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $filhoMaior18Anos, false, true);

        $agendamento = $service->agenda();
        $this->assertInstanceOf(AgendamentoAtendimento::class, $agendamento);
        $this->assertTrue($agendamento->em_tratamento);
    }

    public function test_agenda_servidor_maior_18_anos_pode_agendar()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $servidor = factory(Servidor::class)->create();
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $servidor, false, false);

        $this->assertInstanceOf(AgendamentoAtendimento::class, $service->agenda());
    }

    public function test_agenda_conjuge_pode_agendar()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');
        $conjuge = factory(Dependente::class)->create([
            'data_nascimento' => Carbon::today()->subYear(18)->format('d/m/Y'),
            'grau_parentesco' => GrauParentescoEnum::conjuge()
        ]);
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $conjuge, false, false);

        $this->assertInstanceOf(AgendamentoAtendimento::class, $service->agenda());
    }

    public function test_agenda_filho_maior_18_anos_especial_pode_agendar()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $filhoMaior18Anos = factory(Dependente::class)->create([
            'data_nascimento' => Carbon::today()->subYear(18)->format('d/m/Y'),
            'especial' => true
        ]);
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $filhoMaior18Anos, false, false);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service->agenda());
    }

    public function test_agenda_filho_menor_18_anos_pode_agendar()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $filhoMaior18Anos = factory(Dependente::class)->create([
            'data_nascimento' => Carbon::today()->subYear(5)->format('d/m/Y'),
        ]);
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $filhoMaior18Anos, false, false);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service->agenda());
    }

    public function test_agenda_dependente_na_familia_com_agendamento_aberto_por_especialista_nao_pode_agendar()
    {
        $inicio1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $inicio2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');
        $fim2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:30');

        $servidor = factory(Servidor::class)->create();
        $dependente1 = factory(Dependente::class)->create([
            'dependivel_id' => $servidor,
            'dependivel_type' => Servidor::class
        ]);
        $dependente2 = factory(Dependente::class)->create([
            'dependivel_id' => $servidor,
            'dependivel_type' => Servidor::class
        ]);

        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service1 = new AgendaAtendimentoOdontologicoService($inicio1, $fim1, $prestador, $dependente1, false, false);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service1->agenda());

        $service2 = new AgendaAtendimentoOdontologicoService($inicio2, $fim2, $prestador, $dependente2, false, false);
        $this->expectException(UltimoAtendimentoMuitoProximoException::class);

        $service2->agenda();
    }

    public function test_agenda_dependentes_na_familia_com_agendamento_aberto_em_outro_especialista_pode_agendar_emergencia()
    {
        $inicio1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $inicio2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');
        $fim2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:30');

        $servidor = factory(Servidor::class)->create();
        $dependente1 = factory(Dependente::class)->create([
            'dependivel_id' => $servidor,
            'dependivel_type' => Servidor::class
        ]);
        $dependente2 = factory(Dependente::class)->create([
            'dependivel_id' => $servidor,
            'dependivel_type' => Servidor::class
        ]);

        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service1 = new AgendaAtendimentoOdontologicoService($inicio1, $fim1, $prestador, $dependente1, false, false);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service1->agenda());

        $service2 = new AgendaAtendimentoOdontologicoService($inicio2, $fim2, $prestador, $dependente2, true, false);
        $agendamento = $service2->agenda();
        $this->assertInstanceOf(AgendamentoAtendimento::class, $agendamento);
        $this->assertTrue($agendamento->emergencia);
    }

    public function test_agenda_dependente_na_familia_com_agendamento_aberto_por_especialista_emergencia_e_filho_maior_18_em_tratamento()
    {
        $inicio1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');

        $inicio2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:00');
        $fim2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 11:30');

        $servidor = factory(Servidor::class)->create();
        $filhoMaior18Anos = factory(Dependente::class)->create([
            'dependivel_id' => $servidor,
            'dependivel_type' => Servidor::class,
            'data_nascimento' => Carbon::today()->subYear(18)->format('d/m/Y')
        ]);
        $dependente = factory(Dependente::class)->create([
            'dependivel_id' => $servidor,
            'dependivel_type' => Servidor::class
        ]);

        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $service1 = new AgendaAtendimentoOdontologicoService($inicio2, $fim2, $prestador, $dependente, false, false);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service1->agenda());

        $service2 = new AgendaAtendimentoOdontologicoService($inicio1, $fim1, $prestador, $filhoMaior18Anos, true, true);
        $agendamento2 = $service2->agenda();
        $this->assertInstanceOf(AgendamentoAtendimento::class, $agendamento2);
        $this->assertTrue($agendamento2->em_tratamento);
        $this->assertTrue($agendamento2->emergencia);
    }

    public function test_agenda_dentroHorarioPrestador()
    {
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho(['dia_semana' => DiaSemanaEnum::SEGUNDA()->getName(), 'inicio' => '08:00', 'fim' => '18:00']),
        );

        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');
        $pessoa = factory(Servidor::class)->create();
        $pessoa = factory(Servidor::class)->create();
        $tipoAgendamento = TipoAgendamentoEnum::odontologico();
        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $pessoa, false, false, $tipoAgendamento);

        $this->assertTrue($service->dentroHorarioPrestador());
    }

    public function test_dentroHorarioPrestador_prestador_sem_horarios()
    {
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);

        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');
        $pessoa = factory(Servidor::class)->create();
        $pessoa = factory(Servidor::class)->create();
        $tipoAgendamento = TipoAgendamentoEnum::odontologico();
        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $pessoa, false, false, $tipoAgendamento);

        $this->expectException(PrestadorSemHorariosDefinidosException::class);
        $service->dentroHorarioPrestador();
    }

    public function test_agenda_fora_horario_prestador()
    {
        $inicio = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00'); // Segunda-feira
        $fim = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');
        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho([
                'dia_semana' => DiaSemanaEnum::terca()->getName(), 'inicio' => '08:00', 'fim' => '18:00'
            ]),
        );
        $pessoa = factory(Servidor::class)->create();
        $pessoa = factory(Servidor::class)->create();
        $tipoAgendamento = TipoAgendamentoEnum::odontologico();
        $service = new AgendaAtendimentoOdontologicoService($inicio, $fim, $prestador, $pessoa, false, false, $tipoAgendamento);

        $this->expectException(HorarioIndisponivelForaHorarioPrestadorException::class);

        $service->agenda();
    }

    public function test_agenda_sobrepoe_horario()
    {
        $inicio1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00'); // Segunda-feira
        $fim1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');

        $inicio2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');

        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho([
                'dia_semana' => DiaSemanaEnum::segunda()->getName(), 'inicio' => '08:00', 'fim' => '18:00'
            ]),
        );
        $pessoa1 = factory(Servidor::class)->create();
        $pessoa2 = factory(Servidor::class)->create();
        $tipoAgendamento = TipoAgendamentoEnum::odontologico();
        $service1 = new AgendaAtendimentoOdontologicoService($inicio1, $fim1, $prestador, $pessoa1, false, false, $tipoAgendamento);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service1->agenda());

        $service2 = new AgendaAtendimentoOdontologicoService($inicio2, $fim2, $prestador, $pessoa2, false, false, $tipoAgendamento);
        $this->expectException(HorarioIndisponivelSobrepoeHorarioOdontologicoException::class);

        $service2->agenda();
    }

    public function test_agenda_sobrepoe_horario_emergencia()
    {
        $inicio1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00'); // Segunda-feira
        $fim1 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');

        $inicio2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:00');
        $fim2 = Carbon::createFromFormat('d/m/Y H:i', '25/05/2020 10:30');

        $prestador = factory(PrestadorServico::class)->create([
            'tipo' => TipoPrestadorServicoEnum::dentista(),
            'especialidade' => EspecialidadeOdontologiaEnum::clinico()
        ]);
        $prestador->horariosTrabalho()->save(
            new HorarioTrabalho([
                'dia_semana' => DiaSemanaEnum::segunda()->getName(), 'inicio' => '08:00', 'fim' => '18:00'
            ]),
        );
        $pessoa1 = factory(Servidor::class)->create();
        $pessoa2 = factory(Servidor::class)->create();
        $tipoAgendamento = TipoAgendamentoEnum::odontologico();
        $service1 = new AgendaAtendimentoOdontologicoService($inicio1, $fim1, $prestador, $pessoa1, false, false, $tipoAgendamento);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service1->agenda());

        $service2 = new AgendaAtendimentoOdontologicoService($inicio2, $fim2, $prestador, $pessoa2, true, false, $tipoAgendamento);
        $this->assertInstanceOf(AgendamentoAtendimento::class, $service2->agenda());
    }
}
