<?php

declare(strict_types=1);

namespace Ferienpass\AdminBundle\Tests\Form\Filter;

use Doctrine\ORM\QueryBuilder;
use Ferienpass\AdminBundle\Form\Filter\AccountsFilter;
use Ferienpass\AdminBundle\Form\Filter\AttendancesFilter;
use Ferienpass\AdminBundle\Form\Filter\CompositeFilter;
use Ferienpass\AdminBundle\Form\Filter\ConsentsFilter;
use Ferienpass\AdminBundle\Form\Filter\DebtorFilter;
use Ferienpass\AdminBundle\Form\Filter\EditionFilter;
use Ferienpass\AdminBundle\Form\Filter\HostsFilter;
use Ferienpass\AdminBundle\Form\Filter\OffersFilter;
use Ferienpass\AdminBundle\Form\Filter\OverpaidPaymentsFilter;
use Ferienpass\AdminBundle\Form\Filter\ParticipantFilter;
use Ferienpass\AdminBundle\Form\Filter\PaymentsFilter;
use Ferienpass\AdminBundle\Form\Filter\UnpaidPaymentsFilter;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class ConcreteFiltersTest extends TestCase
{
    // ---- PaymentsFilter ----

    public function testPaymentsFilterGetSortable(): void
    {
        $filter = new PaymentsFilter([]);
        self::assertContains('createdAt', $filter->getSortable());
        self::assertContains('amount', $filter->getSortable());
    }

    public function testPaymentsFilterApplySortingForCreatedAt(): void
    {
        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::once())->method('addOrderBy')->with('i.createdAt', 'DESC')->willReturnSelf();

        (new PaymentsFilter([]))->applySortingFor('createdAt', $qb);
    }

    public function testPaymentsFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new PaymentsFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('payments.filter.%name%', $resolved['label_format']);
    }

    // ---- AccountsFilter ----

    public function testAccountsFilterGetSortable(): void
    {
        $filter = new AccountsFilter([]);
        self::assertContains('createdAt', $filter->getSortable());
        self::assertContains('firstname', $filter->getSortable());
        self::assertContains('lastname', $filter->getSortable());
    }

    public function testAccountsFilterApplySortingForFirstname(): void
    {
        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::once())->method('addOrderBy')->with('i.firstname', 'ASC')->willReturnSelf();

        (new AccountsFilter([]))->applySortingFor('firstname', $qb);
    }

    public function testAccountsFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new AccountsFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('accounts.filter.%name%', $resolved['label_format']);
    }

    // ---- AttendancesFilter ----

    public function testAttendancesFilterGetSortable(): void
    {
        $filter = new AttendancesFilter([]);
        self::assertContains('edition', $filter->getSortable());
        self::assertContains('date', $filter->getSortable());
        self::assertContains('name', $filter->getSortable());
    }

    public function testAttendancesFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new AttendancesFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('participants.filter.%name%', $resolved['label_format']);
    }

    // ---- ConsentsFilter ----

    public function testConsentsFilterGetSortable(): void
    {
        $filter = new ConsentsFilter([]);
        self::assertContains('signedAt', $filter->getSortable());
    }

    public function testConsentsFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new ConsentsFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('signatures.filter.%name%', $resolved['label_format']);
    }

    // ---- HostsFilter ----

    public function testHostsFilterGetSortable(): void
    {
        $filter = new HostsFilter([]);
        self::assertContains('name', $filter->getSortable());
        self::assertContains('createdAt', $filter->getSortable());
        self::assertContains('numberOffers', $filter->getSortable());
    }

    public function testHostsFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new HostsFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('accounts.filter.%name%', $resolved['label_format']);
    }

    // ---- ParticipantFilter ----

    public function testParticipantFilterGetSortable(): void
    {
        $filter = new ParticipantFilter([]);
        self::assertContains('name', $filter->getSortable());
        self::assertContains('createdAt', $filter->getSortable());
    }

    public function testParticipantFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new ParticipantFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('participants.filter.%name%', $resolved['label_format']);
    }

    // ---- DebtorFilter ----

    public function testDebtorFilterGetSortable(): void
    {
        $filter = new DebtorFilter([]);
        self::assertContains('lastname', $filter->getSortable());
    }

    // ---- UnpaidPaymentsFilter ----

    public function testUnpaidPaymentsFilterGetSortable(): void
    {
        $filter = new UnpaidPaymentsFilter([]);
        self::assertContains('lastname', $filter->getSortable());
        self::assertContains('name', $filter->getSortable());
    }

    public function testUnpaidPaymentsFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new UnpaidPaymentsFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('payments.filter.%name%', $resolved['label_format']);
    }

    // ---- OverpaidPaymentsFilter ----

    public function testOverpaidPaymentsFilterGetSortable(): void
    {
        $filter = new OverpaidPaymentsFilter([]);
        self::assertContains('lastname', $filter->getSortable());
        self::assertContains('name', $filter->getSortable());
    }

    public function testOverpaidPaymentsFilterConfigureOptions(): void
    {
        $resolver = new OptionsResolver();
        (new OverpaidPaymentsFilter([]))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('payments.filter.%name%', $resolved['label_format']);
    }

    // ---- CompositeFilter ----

    public function testCompositeFilterGetSortableIsEmpty(): void
    {
        $filter = new CompositeFilter([]);
        self::assertEmpty($filter->getSortable());
    }

    // ---- OffersFilter ----

    public function testOffersFilterGetSortableWithAdminRole(): void
    {
        $security = $this->createMock(Security::class);
        $security->method('isGranted')->with('ROLE_ADMIN')->willReturn(true);

        $filter = new OffersFilter([], $security);
        $sortable = $filter->getSortable();

        self::assertContains('date', $sortable);
        self::assertContains('name', $sortable);
        self::assertContains('host', $sortable);
    }

    public function testOffersFilterGetSortableWithoutAdminRole(): void
    {
        $security = $this->createMock(Security::class);
        $security->method('isGranted')->with('ROLE_ADMIN')->willReturn(false);

        $filter = new OffersFilter([], $security);
        $sortable = $filter->getSortable();

        self::assertContains('date', $sortable);
        self::assertNotContains('host', $sortable);
    }

    public function testOffersFilterConfigureOptions(): void
    {
        $security = $this->createMock(Security::class);
        $resolver = new OptionsResolver();
        (new OffersFilter([], $security))->configureOptions($resolver);
        $resolved = $resolver->resolve([]);
        self::assertSame('offers.filter.%name%', $resolved['label_format']);
    }

    // ---- EditionFilter ----

    public function testEditionFilterGetParent(): void
    {
        $security = $this->createMock(Security::class);
        self::assertSame(EntityType::class, (new EditionFilter($security))->getParent());
    }

    public function testEditionFilterShallDisplayReturnsTrueByDefault(): void
    {
        $security = $this->createMock(Security::class);
        self::assertTrue((new EditionFilter($security))->shallDisplay('admin_attendances_index'));
    }

    public function testEditionFilterShallDisplayReturnsTrueForAdminOffersIndex(): void
    {
        $security = $this->createMock(Security::class);
        $security->method('isGranted')->with('ROLE_ADMIN')->willReturn(true);

        self::assertTrue((new EditionFilter($security))->shallDisplay('admin_offers_index'));
    }

    public function testEditionFilterShallDisplayReturnsFalseForNonAdminOnOffersIndex(): void
    {
        $security = $this->createMock(Security::class);
        $security->method('isGranted')->with('ROLE_ADMIN')->willReturn(false);

        self::assertFalse((new EditionFilter($security))->shallDisplay('admin_offers_index'));
    }

    public function testEditionFilterApplyDoesNothingWhenEmptyAndNotOffersIndex(): void
    {
        $security = $this->createMock(Security::class);

        $parentForm = $this->createMock(FormInterface::class);
        $parentForm->method('getConfig')->willReturn(
            (function () {
                $config = $this->createMock(\Symfony\Component\Form\FormConfigInterface::class);
                $config->method('getOption')->with('view_id')->willReturn('admin_attendances_index');
                return $config;
            })()
        );

        $form = $this->createMock(FormInterface::class);
        $form->method('isEmpty')->willReturn(true);
        $form->method('getName')->willReturn('edition');
        $form->method('getParent')->willReturn($parentForm);

        $qb = $this->createMock(QueryBuilder::class);
        $qb->expects(self::never())->method('andWhere');

        (new EditionFilter($security))->apply($qb, $form);
    }
}
