<?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;

use DateTimeImmutable;
use fkooman\SeCookie\Exception\SessionException;

class FileSessionStorage implements SessionStorageInterface
{
    private string $sessionDir;
    private SerializerInterface $serializer;

    public function __construct(?string $sessionDir = null, ?SerializerInterface $serializer = null)
    {
        if (null === $sessionDir) {
            $sessionDir = \ini_get('session.save_path');
            if (false === $sessionDir || '' === $sessionDir) {
                $sessionDir = sys_get_temp_dir();
            }
        }
        $this->sessionDir = $sessionDir;
        $this->serializer = $serializer ?? new PhpSerializer();
    }

    public function store(ActiveSession $activeSession): void
    {
        $sessionDataString = $this->serializer->serialize(
            array_merge(
                $activeSession->sessionData(),
                [
                    '__expires_at' => $activeSession->expiresAt()->format(DateTimeImmutable::ATOM),
                ]
            )
        );

        if (false === file_put_contents($this->getSessionFileName($activeSession->sessionId()), $sessionDataString)) {
            throw new SessionException('unable to write session file');
        }
    }

    public function retrieve(string $sessionId): ?ActiveSession
    {
        $sessionFile = $this->getSessionFileName($sessionId);
        if (!file_exists($sessionFile)) {
            return null;
        }
        if (false === $sessionDataString = file_get_contents($sessionFile)) {
            return null;
        }

        if (null === $sessionData = $this->serializer->unserialize($sessionDataString)) {
            // we interprete corrupt session data as no session
            $this->destroy($sessionId);

            return null;
        }
        if (!\array_key_exists('__expires_at', $sessionData) || !\is_string($sessionData['__expires_at'])) {
            return null;
        }
        $expiresAt = new DateTimeImmutable($sessionData['__expires_at']);
        // we do not need __expires_at in sessionData, it is part of the object
        unset($sessionData['__expires_at']);

        return new ActiveSession(
            $sessionId,
            $expiresAt,
            $sessionData
        );
    }

    public function destroy(string $sessionId): void
    {
        $sessionFile = $this->getSessionFileName($sessionId);
        if (false === unlink($sessionFile)) {
            throw new SessionException('unable to delete session data');
        }
    }

    private function getSessionFileName(string $sessionId): string
    {
        return sprintf('%s/%s%s', $this->sessionDir, SessionStorageInterface::ID_PREFIX, $sessionId);
    }
}
