<?php

namespace SilverStripe\Control\Tests\Middleware;

use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware;
use SilverStripe\Dev\SapphireTest;

class HTTPCacheControlMiddlewareTest extends SapphireTest
{
    protected function setUp(): void
    {
        parent::setUp();
        // Set to disabled at null forcing level
        HTTPCacheControlMiddleware::config()
            ->set('defaultState', HTTPCacheControlMiddleware::STATE_ENABLED)
            ->set('defaultForcingLevel', 0);
        HTTPCacheControlMiddleware::reset();
    }

    public function provideCacheStates()
    {
        return [
            ['enableCache', false],
            ['publicCache', false],
            ['privateCache', false],
            ['disableCache', true],
        ];
    }

    /**
     * @dataProvider provideCacheStates
     */
    public function testCheckDefaultStates($state, $immutable)
    {
        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->{$state}();

        $response = new HTTPResponse();
        $cc->applyToResponse($response);

        $this->assertStringContainsString('must-revalidate', $response->getHeader('cache-control'));
    }

    /**
     * @dataProvider provideCacheStates
     */
    public function testSetMaxAge($state, $immutable)
    {
        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->{$state}();

        $originalResponse = new HTTPResponse();
        $cc->applyToResponse($originalResponse);

        $cc->setMaxAge('300');

        $response = new HTTPResponse();

        $cc->applyToResponse($response);

        if ($immutable) {
            $this->assertEquals($originalResponse->getHeader('cache-control'), $response->getHeader('cache-control'));
        } else {
            $this->assertStringContainsString('max-age=300', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('no-cache', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('no-store', $response->getHeader('cache-control'));
        }
    }

    public function testEnableCacheWithMaxAge()
    {
        $maxAge = 300;

        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->enableCache(false, $maxAge);

        $response = new HTTPResponse();
        $cc->applyToResponse($response);

        $this->assertStringContainsString('max-age=300', $response->getHeader('cache-control'));
        $this->assertStringNotContainsString('no-cache', $response->getHeader('cache-control'));
        $this->assertStringNotContainsString('no-store', $response->getHeader('cache-control'));
    }

    public function testEnableCacheWithMaxAgeAppliesWhenLevelDoesNot()
    {
        $maxAge = 300;

        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->privateCache(true);
        $cc->enableCache(false, $maxAge);

        $response = new HTTPResponse();
        $cc->applyToResponse($response);

        $this->assertStringContainsString('max-age=300', $response->getHeader('cache-control'));
    }

    public function testPublicCacheWithMaxAge()
    {
        $maxAge = 300;

        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->publicCache(false, $maxAge);

        $response = new HTTPResponse();
        $cc->applyToResponse($response);

        $this->assertStringContainsString('max-age=300', $response->getHeader('cache-control'));
        // STATE_PUBLIC doesn't contain no-cache or no-store headers to begin with,
        // so can't test their removal effectively
        $this->assertStringNotContainsString('no-cache', $response->getHeader('cache-control'));
    }

    public function testPublicCacheWithMaxAgeAppliesWhenLevelDoesNot()
    {
        $maxAge = 300;

        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->privateCache(true);
        $cc->publicCache(false, $maxAge);

        $response = new HTTPResponse();
        $cc->applyToResponse($response);

        $this->assertStringContainsString('max-age=300', $response->getHeader('cache-control'));
    }

    /**
     * @dataProvider provideCacheStates
     */
    public function testSetNoStore($state, $immutable)
    {
        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->setMaxAge('300');
        $cc->setSharedMaxAge('300');

        $cc->{$state}();

        $originalResponse = new HTTPResponse();
        $cc->applyToResponse($originalResponse);

        $cc->setNoStore();

        $response = new HTTPResponse();

        $cc->applyToResponse($response);

        if ($immutable) {
            $this->assertEquals($originalResponse->getHeader('cache-control'), $response->getHeader('cache-control'));
        } else {
            $this->assertStringContainsString('no-store', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('max-age', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('s-maxage', $response->getHeader('cache-control'));
        }
    }

    /**
     * @dataProvider provideCacheStates
     */
    public function testSetNoCache($state, $immutable)
    {
        $cc = HTTPCacheControlMiddleware::singleton();
        $cc->setMaxAge('300');
        $cc->setSharedMaxAge('300');

        $cc->{$state}();

        $originalResponse = new HTTPResponse();
        $cc->applyToResponse($originalResponse);

        $cc->setNoCache();

        $response = new HTTPResponse();

        $cc->applyToResponse($response);

        if ($immutable) {
            $this->assertEquals($originalResponse->getHeader('cache-control'), $response->getHeader('cache-control'));
        } else {
            $this->assertStringContainsString('no-cache', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('max-age', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('s-maxage', $response->getHeader('cache-control'));
        }
    }

    /**
     * @dataProvider provideCacheStates
     */
    public function testSetSharedMaxAge($state, $immutable)
    {
        $cc = HTTPCacheControlMiddleware::singleton();

        $cc->{$state}();

        $originalResponse = new HTTPResponse();
        $cc->applyToResponse($originalResponse);

        $cc->setSharedMaxAge('300');

        $response = new HTTPResponse();

        $cc->applyToResponse($response);

        if ($immutable) {
            $this->assertEquals($originalResponse->getHeader('cache-control'), $response->getHeader('cache-control'));
        } else {
            $this->assertStringContainsString('s-maxage=300', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('no-cache', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('no-store', $response->getHeader('cache-control'));
        }
    }

    /**
     * @dataProvider provideCacheStates
     */
    public function testSetMustRevalidate($state, $immutable)
    {
        $cc = HTTPCacheControlMiddleware::singleton();

        $cc->{$state}();

        $originalResponse = new HTTPResponse();
        $cc->applyToResponse($originalResponse);

        $cc->setMustRevalidate();

        $response = new HTTPResponse();

        $cc->applyToResponse($response);

        if ($immutable) {
            $this->assertEquals($originalResponse->getHeader('cache-control'), $response->getHeader('cache-control'));
        } else {
            $this->assertStringContainsString('must-revalidate', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('max-age', $response->getHeader('cache-control'));
            $this->assertStringNotContainsString('s-maxage', $response->getHeader('cache-control'));
        }
    }

    public function testCachingPriorities()
    {
        $hcc = new HTTPCacheControlMiddleware();
        $this->assertFalse($this->isDisabled($hcc), 'caching starts as disabled');

        $hcc->enableCache();
        $this->assertFalse($this->isDisabled($hcc));

        $hcc->publicCache();
        $this->assertTrue($this->isPublic($hcc), 'public can be set at start');

        $hcc->privateCache();
        $this->assertTrue($this->isPrivate($hcc), 'private overrides public');

        $hcc->publicCache();
        $this->assertFalse($this->isPublic($hcc), 'public does not overrides private');

        $hcc->disableCache();
        $this->assertTrue($this->isDisabled($hcc), 'disabled overrides private');

        $hcc->privateCache();
        $this->assertFalse($this->isPrivate($hcc), 'private does not override disabled');

        $hcc->enableCache(true);
        $this->assertFalse($this->isDisabled($hcc));

        $hcc->publicCache(true);
        $this->assertTrue($this->isPublic($hcc), 'force-public overrides disabled');

        $hcc->privateCache();
        $this->assertFalse($this->isPrivate($hcc), 'private does not overrdie force-public');

        $hcc->privateCache(true);
        $this->assertTrue($this->isPrivate($hcc), 'force-private overrides force-public');

        $hcc->publicCache(true);
        $this->assertFalse($this->isPublic($hcc), 'force-public does not override force-private');

        $hcc->disableCache(true);
        $this->assertTrue($this->isDisabled($hcc), 'force-disabled overrides force-private');

        $hcc->publicCache(true);
        $this->assertFalse($this->isPublic($hcc), 'force-public does not overrides force-disabled');
    }

    protected function isPrivate(HTTPCacheControlMiddleware $hcc)
    {
        return $hcc->hasDirective('private') && !$hcc->hasDirective('public') && !$hcc->hasDirective('no-cache');
    }

    protected function isPublic(HTTPCacheControlMiddleware $hcc)
    {
        return $hcc->hasDirective('public') && !$hcc->hasDirective('private') && !$hcc->hasDirective('no-cache');
    }

    protected function isDisabled(HTTPCacheControlMiddleware $hcc)
    {
        return $hcc->hasDirective('no-store') && !$hcc->hasDirective('private') && !$hcc->hasDirective('public');
    }
}