<?php
namespace Fifa\ConnectId\Sdk\Storage;

use DateTime;
use Fifa\ConnectId\Sdk\Model\PersonDetails;
use Psr\Log\LoggerInterface;
use SQLite3;
use SQLite3Result;

/**
 * Class SqliteStorage
 * @package Fifa\ConnectId\Sdk\Storage
 */
class SqliteStorage implements PersonDetailsStorageInterface
{
    const FILE_NAME = 'person-details.db';

    /**
     * @var SqliteStorage
     */
    private static $instance = null;

    /**
     * @var SQLite3
     */
    private $db;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * Private constructor - blocks possibility of external initialization
     */
    private function __construct(LoggerInterface $logger)
    {
        $this->db = new SQLite3(dirname(__FILE__) . '/' . self::FILE_NAME);
        $this->db->busyTimeout(5000);
        $this->logger = $logger;
    }

    /**
     * String representation of the object
     *
     * @return string
     */
    public function __toString()
    {
        return 'FileName: ' . self::FILE_NAME;
    }

    /**
     * Returns an instance of SqlteStorage class.
     * In case when it doesn't exist yet, it's created
     *
     * @return SqliteStorage
     */
    public static function getInstance(LoggerInterface $logger): SqliteStorage
    {
        if (is_null(self::$instance)) {
            self::$instance = new SqliteStorage($logger);
        }

        return self::$instance;
    }

    /***
     * Puts person details into storage mechanism
     *
     * @param PersonDetails $personDetails
     * @return SQLite3Result|null
     */
    public function put(PersonDetails $personDetails): ?SQLite3Result
    {
        $correlationId = $personDetails->getCorrelationId();
        $storedPersonDetails = $this->getByCorrelationId($correlationId);

        if (!is_null($storedPersonDetails)) {
            $warningMessage = "PersonDetails for correlation id `" . $correlationId . "` are already cached. Details insertion is skipped.";
            $this->logger->warning($warningMessage);
            return null;
        }

        $query = sprintf(
            "INSERT INTO persondetails (correlationId, receiveTime, details, organisationId) VALUES ('%s', '%s', '%s', '%s')",
            SQLite3::escapeString($correlationId),
            time(),
            SQLite3::escapeString($personDetails->getXmlData()),
            SQLite3::escapeString($personDetails->getSourceOrganisationId())
        );

        return $this->db->query($query);
    }

    /**
     * Get person details with given correlation id from storage mechanism
     *
     * @param string $correlationId
     * @return null|PersonDetails
     */
    public function getByCorrelationId(string $correlationId): ?PersonDetails
    {
        $query = sprintf("SELECT * FROM persondetails WHERE correlationId='%s'", SQLite3::escapeString($correlationId));
        $result = $this->db->querySingle($query, true);

        if (!is_null($result) && sizeof($result) > 0) {
            return new PersonDetails($result['organisationId'], $result['details'], $result['correlationId']);
        }

        return null;
    }

    /**
     * Deletes the entries from storage mechanism that are older than given date
     *
     * @param DateTime $dateTime
     * @return SQLite3Result
     */
    public function deleteOlderThan(DateTime $dateTime): SQLite3Result
    {
        $query = sprintf("DELETE FROM persondetails WHERE receiveTime < %s", SQLite3::escapeString($dateTime->getTimestamp()));
        return $this->db->query($query);
    }
}