<?php

declare(strict_types=1);

/*
 * Copyright (c) 2017-2022 François Kooman <fkooman@tuxed.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

namespace fkooman\SeCookie\Tests;

use DateTimeImmutable;
use fkooman\SeCookie\CookieOptions;
use fkooman\SeCookie\SessionOptions;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 *
 * @coversNothing
 */
final class SessionTest extends TestCase
{
    public function testSessionStart(): void
    {
        $testSessionStorage = new TestFileSessionStorage([]);
        $testCookie = new TestCookie(new CookieOptions(), []);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        $testSession->stop();
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
        static::assertSame(
            [
                'Cache-Control: no-store, no-cache, must-revalidate',
                'Pragma: no-cache',
            ],
            $testSession->getHeadersSent()
        );
        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
    }

    public function testSetValue(): void
    {
        $testSessionStorage = new TestFileSessionStorage([]);
        $testCookie = new TestCookie(new CookieOptions(), []);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        $testSession->set('foo', 'bar');
        $testSession->stop();
        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
    }

    public function testGetValue(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ]
        );
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        static::assertSame('bar', $testSession->get('foo'));
        // this is a continuation of a session, so we MUST NOT set a new session cookie
        static::assertSame([], $testCookie->getHeadersSent());
    }

    public function testGetSet(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:15:00+00:00',
                ],
            ]
        );
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));
        $testSession->start();
        static::assertSame('bar', $testSession->get('foo'));
        $testSession->set('oof', 'baz');
        static::assertSame('baz', $testSession->get('oof'));
        $testSession->stop();
        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    'foo' => 'bar',
                    'oof' => 'baz',
                    '__expires_at' => '2019-01-01T08:15:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
    }

    public function testUnsetValue(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ]
        );
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        static::assertSame('bar', $testSession->get('foo'));
        $testSession->remove('foo');
        $testSession->stop();

        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
    }

    public function testNonExistingSession(): void
    {
        $testSessionStorage = new TestFileSessionStorage([]);
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        $testSession->stop();
        // we expect a new cookie to be sent as the provided cookie does not have an active session
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
    }

    public function testExpiredSession(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ]
        );
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        // session should have been expired 15 minutes ago...
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:45:00+00:00'));

        $testSession->start();
        static::assertNull($testSession->get('foo'));
        $testSession->stop();
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    '__expires_at' => '2019-01-01T09:15:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
    }

    public function testDestroy(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ]
        );
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        $testSession->destroy();
        static::assertSame([], $testSessionStorage->getAll());
    }

    public function testMultipleSessions(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => [
                    'foo' => 'bar',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    'foo' => 'baz',
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ]
        );
        $testCookie = new TestCookie(
            new CookieOptions(),
            [
                'SID' => '0000000000000000000000000000000000000000000000000000000000000000',
                'TID' => '0101010101010101010101010101010101010101010101010101010101010101',
            ]
        );
        $testSessionOne = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));
        $testSessionTwo = new TestSession(SessionOptions::init()->withName('TID'), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSessionOne->start();
        static::assertSame('bar', $testSessionOne->get('foo'));

        $testSessionTwo->start();
        static::assertSame('baz', $testSessionTwo->get('foo'));

        // this is a continuation of a sessions, so we MUST NOT set a new session cookie
        static::assertSame([], $testCookie->getHeadersSent());
    }

    public function testMalformedSessionFile(): void
    {
        $testSessionStorage = new TestFileSessionStorage(
            [
                '0000000000000000000000000000000000000000000000000000000000000000' => null,
            ]
        );
        $testCookie = new TestCookie(new CookieOptions(), ['SID' => '0000000000000000000000000000000000000000000000000000000000000000']);
        $testSession = new TestSession(new SessionOptions(), $testCookie, $testSessionStorage, new DateTimeImmutable('2019-01-01T08:00:00+00:00'));

        $testSession->start();
        $testSession->stop();
        static::assertSame(
            [
                'Set-Cookie: SID=0101010101010101010101010101010101010101010101010101010101010101; HttpOnly; SameSite=Lax; Secure',
            ],
            $testCookie->getHeadersSent()
        );
        static::assertSame(
            [
                '0101010101010101010101010101010101010101010101010101010101010101' => [
                    '__expires_at' => '2019-01-01T08:30:00+00:00',
                ],
            ],
            $testSessionStorage->getAll()
        );
    }
}
