<?php

declare(strict_types=1);

namespace Dealerdirect\Agreements\Tests;

use Dealerdirect\Agreements\DataObjects\DataObject;
use Dealerdirect\Agreements\Enums\Locale;
use Dealerdirect\Agreements\Pdf\Templates\AbstractTemplate;
use Dealerdirect\Agreements\Utils\Translator;
use PHPUnit\Framework\MockObject\Stub;

abstract class TestCase extends \PHPUnit\Framework\TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        \DG\BypassFinals::enable();
    }

    public function assertStringIsValidHtml(string $html, string $message = ''): void
    {
        libxml_use_internal_errors(true);
        $dom = new \DOMDocument();
        $dom->loadHTML($html, LIBXML_HTML_NOIMPLIED + LIBXML_HTML_NODEFDTD);

        // Check for no errors
        $this->assertCount(0, libxml_get_errors());

        $this->assertInstanceOf(\DOMDocumentType::class, $dom->doctype, 'No doctype');

        // Check for html, head, and body nodes
        $xpath = new \DOMXPath($dom);
        $this->assertCount(1, $xpath->query('/html'), 'No <html> node');
        $this->assertCount(1, $xpath->query('/html/head'), 'No valid <head> node');
        $this->assertCount(1, $xpath->query('/html/body'), 'No valid <body> node');
    }

    protected function getResourcesPath(): string
    {
        return __DIR__ . '/resources';
    }

    protected function displayValue(mixed $value): string
    {
        return match ($value) {
            null => 'null',
            true => 'true',
            false => 'false',
            default => '' === $value ? "''" : (string) $value,
        };
    }

    protected function getMockForTranslator(): \Dealerdirect\Agreements\Utils\Translator|Stub
    {
        $translator = $this->createStub(\Dealerdirect\Agreements\Utils\Translator::class);
        $translator->method('get')->willReturn('translated string');

        return $translator;
    }

    protected function getMockForStorage(): \Illuminate\Contracts\Filesystem\Filesystem|Stub
    {
        $storage = $this->createStub(\Illuminate\Contracts\Filesystem\Filesystem::class);
        $storage->method('directories')->willReturn(['purchase-agreements']);
        $storage->method('exists')->willReturn(true);

        return $storage;
    }

    /**
     * @param array<string,array<string,string>> $translations
     */
    protected function getTranslatorInstance(array $translations = []): \Dealerdirect\Agreements\Utils\Translator
    {
        return new \Dealerdirect\Agreements\Utils\Translator(
            \Dealerdirect\Agreements\Enums\Locale::NL_NL,
            array_merge([
                'general' => [],
                'options' => [],
                'vehicle' => [],
                'sales_agreement' => [],
                'labels' => require $this->getResourcesPath() . '/lang/labels-nl_NL.php',
            ], $translations)
        );
    }

    /**
     * @param array<string,mixed> $attributes
     */
    protected function getMockForDataObject(
        array $attributes = []
    ): \Dealerdirect\Agreements\DataObjects\DataObject|Stub {
        return new class ($attributes) implements \Dealerdirect\Agreements\DataObjects\DataObject {
            /**
             * @param array<string,mixed> $attributes
             */
            public function __construct(private array $attributes)
            {
            }

            public function __get(string $name): mixed
            {
                return $this->attributes[$name] ?? null;
            }
        };
    }

    protected function getMockForQueryBuilder(): \Doctrine\DBAL\Query\QueryBuilder|Stub
    {
        $queryBuilder = $this->createStub(\Doctrine\DBAL\Query\QueryBuilder::class);
        $queryBuilder->method('from')->willReturnSelf();
        $queryBuilder->method('select')->willReturnSelf();
        $queryBuilder->method('where')->willReturnSelf();
        $queryBuilder->method('setParameter')->willReturnSelf();
        $queryBuilder->method('fetchOne')->willReturn(null);

        return $queryBuilder;
    }

    /**
     * @param array<string|int,mixed> $expected
     * @param array<string|int,mixed> $actual
     */
    protected function assertHasKeyAndValue(array $expected, array $actual): void
    {
        foreach ($expected as $key => $value) {
            $this->assertArrayHasKey($key, $actual, "Key '$key' not found in array");
            $this->assertEquals($value, $actual[$key], "Value for key '$key' does not match");
        }
    }

    protected function getTemplateMock(
        ?DataObject $dataObject = null,
        ?Translator $translator = null,
        ?string $template = null
    ): AbstractTemplate {
        $dataObject ??= $this->getMockForDataObject();
        $translator ??= $this->getMockForTranslator();

        /** @var AbstractTemplate|\PHPUnit\Framework\MockObject\MockObject $template */
        return (new class (
            Locale::NL_NL,
            $dataObject,
            $translator,
            $this->getResourcesPath()
        ) extends AbstractTemplate {
            private string $template;

            public function getTemplate(): string
            {
                return $this->template;
            }

            public function setTemplate(string $template): self
            {
                $this->template = $template;

                return $this;
            }
        })->setTemplate($template ?? 'foo-bar.php');
    }

    /**
     * @param mixed[] $array
     */
    protected static function randomItem(array $array, int $amount = 1): mixed
    {
        if (1 === $amount) {
            return $array[array_rand($array)];
        }

        $result = [];
        for ($i = 0; $i < $amount; $i++) {
            $result[] = $array[array_rand($array)];
        }

        return $result;
    }
}
