<?php

declare(strict_types=1);

namespace Dealerdirect\Agreements\Tests\Models;

use Dealerdirect\Agreements\DataObjects\PurchaseAgreement;
use Dealerdirect\Agreements\DataObjects\SalesAgreement;
use Dealerdirect\Agreements\Exceptions\DeliveryNotFoundException;
use Dealerdirect\Agreements\Models\AbstractModel;
use Doctrine\DBAL\Connection;

/**
 * @extends \PHPUnit\Framework\TestCase <extended>
 *
 * @uses \Dealerdirect\Agreements\DataObjects\Conditions
 */
trait TestCases
{
    public function test_log_table_is_correct(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->expects($this->once())
            ->method('insert')
            ->with($this->stringContains($this->getLogTable()))
            ->willReturn(1)
        ;

        $this->getModel($connection)->log('test');
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_sale_data_uses_the_correct_table(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->expects($this->once())
            ->method('fetchAssociative')
            ->with($this->stringContains("FROM {$this->getTable()}"))
            ->willReturn($this->fixtureFetchSaleData())
        ;

        $this->getModel($connection)->getSaleData();
    }

    /**
     * @covers \Dealerdirect\Agreements\Exceptions\DeliveryNotFoundException
     */
    public function test_get_sale_data_should_throw_exception_when_deliver_id_not_found(): void
    {
        $this->expectException(DeliveryNotFoundException::class);

        $connection = $this->createMock(Connection::class);

        $this->getModel($connection)->getSaleData();
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_sale_data_should_return_sales_agreement(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->method('fetchAssociative')
            ->willReturn($this->fixtureFetchSaleData())
        ;

        $model = $this->getModel($connection);

        $this->assertInstanceOf(SalesAgreement::class, $model->getSaleData());
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_sale_data_has_the_correct_columns(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->method('fetchAssociative')
            ->willReturnCallback(
                function (string $query) {
                    $data = $this->fixtureFetchSaleData();

                    foreach (array_keys($data) as $key) {
                        $this->assertStringContainsString($key, $query);
                    }

                    return $data;
                }
            )
        ;

        $this->getModel($connection)->getSaleData();
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_sale_data_will_return_valid_inspection_date(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->method('fetchAssociative')
            ->willReturn($this->fixtureFetchSaleData(inspectionValidUntil: new \DateTime('2021-01-01')))
        ;

        $model = $this->getModel($connection);

        $saleData = $model->getSaleData();
        $this->assertInstanceOf(SalesAgreement::class, $saleData);
        $this->assertEquals('2021-01-01', $saleData->vehicle->inspectionValidUntil->format('Y-m-d'));
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_purchase_query_uses_the_correct_table(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->expects($this->once())
            ->method('fetchAssociative')
            ->with($this->stringContains("FROM {$this->getTable()}"))
            ->willReturn($this->fixtureFetchPurchaseData())
        ;

        $connection->method('createQueryBuilder')->willReturn($this->getMockForQueryBuilder());

        $this->getModel($connection)->getPurchaseData();
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_purchase_data_should_return_purchase_agreement(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->method('createQueryBuilder')->willReturn($this->getMockForQueryBuilder());
        $connection->method('fetchAssociative')
            ->willReturn($this->fixtureFetchPurchaseData())
        ;


        $model = $this->getModel($connection);

        $this->assertInstanceOf(PurchaseAgreement::class, $model->getPurchaseData());
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_get_purchase_data_has_the_correct_columns(): void
    {
        $connection = $this->createMock(Connection::class);
        $connection->method('fetchAssociative')
            ->willReturnCallback(
                function (string $query) {
                    $data = $this->fixtureFetchPurchaseData();

                    foreach (array_keys($data) as $key) {
                        $this->assertStringContainsString($key, $query);
                    }

                    return $data;
                }
            )
        ;

        $connection->method('createQueryBuilder')->willReturn($this->getMockForQueryBuilder());

        $this->getModel($connection)->getPurchaseData();
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_checklist_will_be_null_when_survey_results_are_empty_from_db(): void
    {
        $queryBuilder = $this->createPartialMock(\Doctrine\DBAL\Query\QueryBuilder::class, ['fetchAssociative']);
        $queryBuilder->method('fetchAssociative')->willReturn(null);

        $connection = $this->createMock(Connection::class);
        $connection->method('createQueryBuilder')->willReturn($queryBuilder);
        $connection->method('fetchAssociative')->willReturn(
            $this->fixtureFetchSaleData(checklistId: 123456),
        );

        $model = $this->getModel($connection);
        $saleData = $model->getSaleData();
        $checklist = $saleData->vehicle->getChecklist($saleData->locale);

        $this->assertInstanceOf(SalesAgreement::class, $saleData);
        $this->assertNull($checklist);
    }

    /**
     * @uses \Dealerdirect\Agreements\DataObjects\Conditions
     */
    public function test_checklist_will_be_fetched_correctly(): void
    {
        $queryBuilder = $this->createPartialMock(
            \Doctrine\DBAL\Query\QueryBuilder::class,
            ['fetchAssociative', 'fetchAllAssociative']
        );
        $queryBuilder->method('fetchAssociative')->willReturn([
            'id' => 1,
            'survey_id' => 1,
            'result' => '{"2": {"answer": "answer"}}',
            'created_at' => '2021-01-01',
            'updated_at' => null,
        ]);
        $queryBuilder->method('fetchAllAssociative')->willReturn([
            [
                'id' => 1,
                'survey_id' => 1,
                'question' => '{"nl_NL":"question"}',
                'options' => '{"type":"date"}',
            ],
            [
                'id' => 2,
                'survey_id' => 1,
                'question' => '{"nl_NL":"question"}',
                'options' => '{"type":"text"}',
            ],
        ]);

        $connection = $this->createMock(Connection::class);
        $connection->method('createQueryBuilder')->willReturn($queryBuilder);
        $connection->method('fetchAssociative')->willReturn(
            $this->fixtureFetchSaleData(checklistId: 123456),
        );

        $model = $this->getModel($connection);
        $saleData = $model->getSaleData();
        $checklist = $saleData->vehicle->getChecklist($saleData->locale);

        $this->assertInstanceOf(SalesAgreement::class, $saleData);
        $this->assertIsArray($checklist);
        $this->assertNotEmpty($checklist);
    }

    /**
     * @return array<string,string|int|null>
     */
    public static function fixtureFetchSaleData(
        ?int $checklistId = null,
        ?\DateTime $inspectionValidUntil = null
    ): array {
        return [
            'lot_id' => 1,
            'site_reference' => 1,
            'consumer_first_letters' => 'A',
            'consumer_name' => 'B',
            'consumer_salutation' => 0,
            'consumer_street_name' => 'D',
            'consumer_house_number' => 'E',
            'consumer_zip_code' => 'F',
            'consumer_city' => 'G',
            'consumer_country' => 1,
            'consumer_phone' => 'H',
            'consumer_mobile' => 'I',
            'consumer_email' => 'J',
            'make' => 'make',
            'model' => 'model',
            'license_plate' => 'license_plate',
            'bodywork' => 0,
            'execution' => 'execution',
            'engine_capacity' => 0,
            'fuel' => 1,
            'power' => 0,
            'power_2' => null,
            'transmission' => 1,
            'mileage' => 1,
            'construction_year' => 2000,
            'inspection_valid_until' => $inspectionValidUntil ? $inspectionValidUntil->format('Y-m-d') : null,
            'color' => null,
            'paint_type' => null,
            'upholstery' => null,
            'drivable' => 1,
            'condition_exterior' => null,
            'condition_interior' => null,
            'condition_technical' => null,
            'condition_tire_profile' => null,
            'damage_free' => null,
            'internal_comments' => 'internal_comments',
            'consumer_comments' => 'consumer_comments',
            'vehicle_options' => '1;2;3',
            'pick_up' => 1,
            'marge' => 1,
            'first_ascription' => null,
            'checklist_id' => $checklistId,
            'company_name' => 'company_name',
            'company_street_name' => 'company_street_name',
            'company_house_number' => 'company_house_number',
            'company_zip_code' => 'company_zip_code',
            'company_country' => 1,
            'company_city' => 'company_city',
            'company_phone' => 'company_phone',
            'sales_date' => '2021-01-01',
            'sales_agent' => 1,
            'sales_agent_name' => 'sales_agent_name',
            'sales_price' => 1,
            'sales_fee' => 1,
            'company_id' => 1,
            'company_agree_email' => 'company_agree_email',
            'company_agree_email_2' => 'company_agree_email_2',
            'wok_notification' => 0,
            'registration_possible' => 1,
        ];
    }

    /**
     * @return array<string,string>
     */
    public static function fixtureFetchPurchaseData(): array
    {
        return [
            'lot_id' => 1,
            'site_reference' => 1,
            'consumer_first_letters' => 'A',
            'consumer_name' => 'consumer_name',
            'consumer_street_name' => 'consumer_street_name',
            'consumer_house_number' => 'consumer_house_number',
            'consumer_zip_code' => 'consumer_zip_code',
            'consumer_city' => 'consumer_city',
            'consumer_country' => 1,
            'consumer_phone' => 'consumer_phone',
            'consumer_mobile' => 'consumer_mobile',
            'consumer_email' => 'consumer_email',
            'license_plate' => 'license_plate',
            'make' => 'make',
            'model' => 'model',
            'execution' => 'execution',
            'pick_up' => 1,
            'bodywork' => 0,
            'construction_year' => 2000,
            'mileage' => 1,
            'company_id' => 1,
            'company_name' => 'company_name',
            'company_street_name' => 'company_street_name',
            'company_house_number' => 'company_house_number',
            'company_zip_code' => 'company_zip_code',
            'company_country' => 1,
            'company_city' => 'company_city',
            'company_phone' => 'company_phone',
            'company_language' => 1,
            'company_agree_email' => 'company_agree_email',
            'company_agree_email_2' => 'company_agree_email_2',
            'sales_agent_name' => 'sales_agent_name',
            'sales_agent' => 1,
            'sales_price' => 1,
            'sales_fee' => 1,
            'sales_date' => '2021-01-01',
            'internal_comments' => 'internal_comments',
            'consumer_comments' => 'consumer_comments',
            'checklist_id' => null,
        ];
    }

    abstract protected function getModel(Connection $connection, ?int $deliverId = 1): AbstractModel;

    abstract protected function getTable(): string;

    abstract protected function getLogTable(): string;
}
