<?php

namespace Opentalent\OtStats\Domain\Repository;

use Opentalent\OtCore\Website\OtPageRepository;
use Opentalent\OtCore\Website\OtWebsiteRepository;
use Opentalent\OtStats\Domain\Model\MatomoWebsite;
use PDO;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;

class MatomoWebsiteRepository
{
    /**
     * Database connection data
     */
    const MATOMO_DB_HOST = 'stats';
    const MATOMO_DB_NAME = 'matomo';
    const MATOMO_DB_USER = 'matomo';
    const MATOMO_DB_PWD = ']:*j8GYU/n9mp+';

    /**
     * Regex
     */
    const RELATIVE_PATH_VALIDATION = '/^\/[a-zA-Z0-9_\-]+\/?$/';
    const DOMAIN_VALIDATION = "/^(https?:\/\/)?([\w-]+\.)+[a-zA-Z]{2,}(:\d+)?(\/[a-zA-Z0-9_\-]+)*\/?$/";

    /**
     * Connection to the matomo DB
     *
     * @var PDO
     */
    private PDO $matomoCnn;

    /**
     * @var \TYPO3\CMS\Core\Database\ConnectionPool
     */
    private ConnectionPool $connectionPool;

    public function injectConnectionPool(\TYPO3\CMS\Core\Database\ConnectionPool $connectionPool)
    {
        $this->connectionPool = $connectionPool;
    }

    public function __construct(
        private readonly OtPageRepository $otPageRepository,
        private readonly OtWebsiteRepository $otWebsiteRepository
    )
    {
        // Connection to the Matomo DB
        $this->matomoCnn = new PDO(
            "mysql:host=" . self::MATOMO_DB_HOST . ";dbname=" . self::MATOMO_DB_NAME,
            self::MATOMO_DB_USER,
            self::MATOMO_DB_PWD,
            array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')
        );
        $this->matomoCnn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    /**
     * Create a new MatomoWebsite from an array
     *
     * @param array $data
     * @return MatomoWebsite
     */
    private function fromArray(array $data): MatomoWebsite
    {
        $matomoSite = new MatomoWebsite();
        $matomoSite->setId($data['idsite']);
        $matomoSite->setName($data['name']);
        $matomoSite->setMainUrl($data['main_url']);
        $matomoSite->setExcludedIps($data['exluded_ips']);
        $matomoSite->setType($data['type']);
        $matomoSite->setCreatorLogin($data['creator_login']);
        return $matomoSite;
    }

    /**
     * Retrieve the MatomoWebsite object by id, otherwise, returns Null
     *
     * @param int $id
     * @return MatomoWebsite|null
     */
    public function findById(int $id): ?MatomoWebsite
    {
        $stmt = $this->matomoCnn->prepare(
            "SELECT s.idsite, s.name, s.main_url, s.excluded_ips, s.type, s.creator_login
                      FROM matomo.matomo_site s
                      WHERE s.idsite=" . $id . ";"
        );
        $stmt->execute();
        $stmt->setFetchMode(PDO::FETCH_ASSOC);
        $data = $stmt->fetch();
        if (!$data) {
            return null;
        }
        return $this->fromArray($data);
    }

    /**
     * Retrieve the MatomoWebsite object by uri, otherwise, returns Null
     *
     * @param string $uri
     * @return MatomoWebsite|null
     */
    public function findByUri(string $uri): ?MatomoWebsite
    {
        $stmt = $this->matomoCnn->prepare(
            "SELECT s.idsite, s.name, s.main_url, s.excluded_ips, s.type, s.creator_login
                      FROM matomo.matomo_site s
                      WHERE s.main_url='" . $uri . "';"
        );
        $stmt->execute();
        $stmt->setFetchMode(PDO::FETCH_ASSOC);
        $data = $stmt->fetch();
        if (!$data) {
            return null;
        }
        return $this->fromArray($data);
    }

    /**
     * Retrieve the MatomoWebsite object for the typo3 website with
     * the given rootUid, otherwise, returns Null
     *
     * @param int $websiteUid
     * @return MatomoWebsite|null
     */
    public function findByWebsiteUid(int $websiteUid): ?MatomoWebsite
    {
        $otWebsiteRepository = GeneralUtility::makeInstance(ObjectManager::class)->get(OtWebsiteRepository::class);
        $website = $otWebsiteRepository->getWebsiteByUid($websiteUid);

        if ($website['matomo_site_id'] == null) {
            return null;
        }
        return $this->findById($website['matomo_site_id']);
    }

    /**
     * Retrieve the MatomoWebsite object for the typo3 website with
     * the given rootUid, otherwise, returns Null
     *
     * @param int $rootUid
     * @return MatomoWebsite|null
     */
    public function findByRootUid(int $rootUid): ?MatomoWebsite
    {
        $otWebsiteRepository = GeneralUtility::makeInstance(ObjectManager::class)->get(OtWebsiteRepository::class);
        $website = $otWebsiteRepository->getWebsiteByPageUid($rootUid);

        if ($website['matomo_site_id'] == null) {
            return null;
        }
        return $this->findById($website['matomo_site_id']);
    }

    /**
     * Create a new matomo's website for the Typo3 site (or retrieve the existing if any),
     * store its id in the Typo3 DB and return it
     *
     * @param int $rootUid
     * @return int|string The matomo site's id
     * @throws \Exception
     */
    public function createFor(int $rootUid) {
        // Just to make sure this page is actually the root page
        $rootPage = $this->otPageRepository->getRootPageFor($rootUid);
        $rootUid = $rootPage['uid'];

        $website = $this->otWebsiteRepository->getWebsiteByUid($rootPage['ot_website_uid']);

        // Retrieve current site informations
        $isDev = ($_SERVER['TYPO3_CONTEXT'] == 'Development');

        $title = $website['subdomain'];
        if ($isDev) {
            $title = $title . "_DEV";
        }

        if ($isDev) {
            $uri = rtrim($_SERVER['HTTP_HOST'], '/') . '/' . $website['subdomain'];
        } else {
            $uri = $this->otWebsiteRepository->resolveWebsiteDomain($website);
        }

        if (!preg_match(self::DOMAIN_VALIDATION, $uri)) {
            throw new \RuntimeException("The domain's name is not valid: " . $uri);
        }

        // Make sure the current typo3 website has no existing configured matomo website
        if ($website['matomo_site_id'] != null) {
            // Check if the registered matomo site still exist. If it does, throw an error.
            if ($this->findById($website['matomo_site_id']) != null) {
                throw new \RuntimeException('This website has already been registered (matomo id: ' . $website['matomo_site_id'] . ')');
            }
        }

        // Check if there is already an existing matomo record for this url
        $existingMatomoSite = $this->findByUri($uri);
        if ($existingMatomoSite) {
            // Verify that the names match (just to be sure)
            if ($existingMatomoSite->getName() != $title) {
                throw new \RuntimeException('A website with this URL has already been registered: ' . $uri);
            }
            $matomoSiteId = $existingMatomoSite->getId();
        }

        // Create a new website record in the matomo DB, and store its id
        $this->matomoCnn->beginTransaction();
        $this->connectionPool->getConnectionByName('Default')->beginTransaction();
        try {
            $ignoreIps = $isDev ? '' : '10.8.0.*';

            if ($existingMatomoSite == null) {
                // Create the new website
                $stmt = $this->matomoCnn->prepare(
                    "INSERT INTO matomo.matomo_site
                          (`name`, main_url, ts_created, ecommerce, sitesearch, sitesearch_keyword_parameters, 
                           sitesearch_category_parameters, timezone, 
                           currency, excluded_ips, excluded_parameters, excluded_user_agents, excluded_referrers,
                           `group`, `type`, creator_login)
                          VALUES ('" . $title . "', '" . $uri . "', NOW(), 0, 1, '', '', 'Europe/Paris', 
                                  'EUR', '" . $ignoreIps . "', '', '', '', '', 'website', 'typo3');");
                $stmt->execute();

                // Get the new site's id
                $matomoSiteId = $this->matomoCnn->lastInsertId();

                # Give the right to the typo3 user on this new site
                $stmt = $this->matomoCnn->prepare(
                    "INSERT INTO matomo.matomo_access
                          (login, idSite, access)
                          VALUES ('typo3', $matomoSiteId, 'view');");
                $stmt->execute();
            }

            // Update typo3 DB
            $queryBuilder = $this->connectionPool->getQueryBuilderForTable('ot_websites');
            $queryBuilder
                ->update('ot_websites')
                ->where($queryBuilder->expr()->eq('uid', $website['uid']))
                ->set('matomo_site_id', $matomoSiteId)
                ->execute();

            // finalize
            $this->matomoCnn->commit();
            $this->connectionPool->getConnectionByName('Default')->commit();

            return $matomoSiteId;

        } catch (\Exception $e) {
            // An error happened, rollback and throw
            $this->matomoCnn->rollBack();
            $this->connectionPool->getConnectionByName('Default')->rollback();
            throw $e;
        }
    }

    /**
     * Disable the matomo monitoring for the website
     * > Data wont be deleted from the matomo DB, but no more data will
     *   be collected unless it is reactivated with the 'createFor' method
     *
     * @param int $rootUid
     * @throws \Exception
     */
    public function disableFor(int $rootUid) {
        $matomoSite = $this->findByRootUid($rootUid);
        if ($matomoSite == null) {
            throw new \RuntimeException('Activity monitoring is already disabled for this website');
        }

        // Set up a connection to the typo3 DB
        $this->connectionPool->getConnectionByName('Default')->beginTransaction();
        try {
            // Update typo3 DB
            $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
            $queryBuilder
                ->update('ot_websites')
                ->where($queryBuilder->expr()->eq('matomo_site_id', $matomoSite->getId()))
                ->set('matomo_site_id', null)
                ->execute();

            // finalize
            $this->connectionPool->getConnectionByName('Default')->commit();

        } catch (\Exception $e) {
            // An error happened, rollback and throw
            $this->connectionPool->getConnectionByName('Default')->rollback();
            throw $e;
        }
    }
}
