<?php

namespace yiiunit\framework\behaviors;

use Exception;
use PHPUnit\Framework\MockObject\MockObject;
use yii\base\Widget;
use yii\behaviors\CacheableWidgetBehavior;
use yii\caching\ArrayCache;
use yiiunit\TestCase;

/**
 * Unit test for [[\yii\behaviors\CacheableWidgetBehavior]].
 *
 * @see CacheableWidgetBehavior
 * @group behaviors
 */
class CacheableWidgetBehaviorTest extends TestCase
{
    /**
     * Default-initialized simple cacheable widget mock.
     */
    private MockObject|SimpleCacheableWidget|CacheableWidgetBehavior|null $simpleWidget = null;

    /**
     * Default-initialized dynamic cacheable widget mock.
     */
    private MockObject|SimpleCacheableWidget|CacheableWidgetBehavior|null $dynamicWidget = null;

    /**
     * {@inheritdoc}
     */
    protected function setUp(): void
    {
        $this->initializeApplicationMock();
        $this->initializeWidgetMocks();
    }

    /**
     * @throws Exception
     */
    public function testWidgetIsRunWhenCacheIsEmpty(): void
    {
        $this->simpleWidget
            ->expects($this->once())
            ->method('run');

        $contents = $this->simpleWidget->test();
        $this->assertEquals('contents', $contents);
    }

    /**
     * @throws Exception
     */
    public function testWidgetIsNotRunWhenCacheIsNotEmpty(): void
    {
        $this->simpleWidget->cacheDuration = 0;
        $this->simpleWidget
            ->expects($this->once())
            ->method('run');

        for ($counter = 0; $counter <= 1; $counter++) {
            $this->assertEquals('contents', $this->simpleWidget->test());
        }
    }

    /**
     * @throws Exception
     */
    public function testDynamicContent(): void
    {
        $this->dynamicWidget->cacheDuration = 0;
        $this->dynamicWidget
            ->expects($this->once())
            ->method('run');

        for ($counter = 0; $counter <= 1; $counter++) {
            $expectedContents = sprintf('<div>dynamic contents: %d</div>', $counter);
            $this->assertEquals($expectedContents, $this->dynamicWidget->test());
        }
    }

    /**
     * Initializes a mock application.
     *
     */
    private function initializeApplicationMock(): void
    {
        $this->mockApplication([
            'components' => [
                'cache' => [
                    'class' => ArrayCache::class,
                ],
            ],
            'params' => [
                // Counter for dynamic contents testing.
                'counter' => 0,
            ],
        ]);
    }

    /**
     * Initializes mock widgets.
     *
     */
    private function initializeWidgetMocks(): void
    {
        $this->simpleWidget = $this->getWidgetMock(SimpleCacheableWidget::class);
        $this->dynamicWidget = $this->getWidgetMock(DynamicCacheableWidget::class);
    }

    /**
     * Returns a widget mock.
     */
    private function getWidgetMock(string $widgetClass): MockObject
    {
        return $this->getMockBuilder($widgetClass)
            ->onlyMethods(['run'])
            ->enableOriginalConstructor()
            ->enableProxyingToOriginalMethods()
            ->getMock();
    }
}

class BaseCacheableWidget extends Widget
{
    /**
     * {@inheritdoc}
     */
    public function test()
    {
        ob_start();
        ob_implicit_flush(false);
        try {
            $out = '';
            if ($this->beforeRun()) {
                $result = $this->run();
                $out = $this->afterRun($result);
            }
        } catch (Exception $exception) {
            if (ob_get_level() > 0) {
                ob_end_clean();
            }
            throw $exception;
        }

        return ob_get_clean() . $out;
    }

    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'cacheable' => CacheableWidgetBehavior::class,
        ];
    }
}

class SimpleCacheableWidget extends BaseCacheableWidget
{
    /**
     * {@inheritdoc}
     */
    public function run()
    {
        $content = 'contents';

        return $content;
    }
}

class DynamicCacheableWidget extends BaseCacheableWidget
{
    /**
     * {@inheritdoc}
     */
    public function run()
    {
        $dynamicContentsExpression = 'return "dynamic contents: " . \Yii::$app->params["counter"]++;';
        $dynamicContents = $this->view->renderDynamic($dynamicContentsExpression);
        $content = '<div>' . $dynamicContents . '</div>';

        return $content;
    }
}
