<?php

declare(strict_types=1);

/*
 * This file is part of memberfiles bundle.
 *
 * (c) vonRotenberg
 *
 * @license commercial
 */

namespace vonRotenberg\MemberfilesBundle\Cron;

use Contao\BackendUser;
use Contao\Controller;
use Contao\CoreBundle\Monolog\ContaoContext;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\CoreBundle\ServiceAnnotation\CronJob;
use Contao\DataContainer;
use Contao\Dbafs;
use Contao\File;
use Contao\Files;
use Contao\FilesModel;
use Contao\MemberModel;
use Contao\PageModel;
use Contao\StringUtil;
use Contao\System;
use Doctrine\DBAL\Connection;
use NotificationCenter\Model\Notification;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use vonRotenberg\MemberfilesBundle\Model\MemberfilesConfigModel;

class SecureDownloadsJob
{

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

    /** @var Connection */
    protected $db;

    public function __construct(Connection $db, LoggerInterface $logger)
    {
        $this->logger = $logger;
        $this->db = $db;
    }

    /**
     * @CronJob("*\/10 * * * *")
     */
    public function sendFileNotifications(string $scope)
    {
        // Get archives with notifications enabled
        $Configurations = MemberfilesConfigModel::findBy(["hasNotification = ?", "enabled = ?"], ['1','1']);

        if ($Configurations !== null)
        {
            while ($Configurations->next())
            {
                // Do we have new items
                $Members = $this->db->executeQuery("SELECT s.pid FROM tl_member_secureDownloads s INNER JOIN tl_files f ON f.uuid = s.uuid WHERE s.nc_sent != '1' GROUP BY s.pid");

                // Load groups and notification models if we have news to share
                if ($Members->rowCount() && ($Notification = Notification::findByPk($Configurations->sd_nc_notification)) !== null)
                {
                    foreach ($Members->iterateAssociative() as $member)
                    {
                        if (($Member = MemberModel::findOneBy(array("disable != '1'","login = '1'","email LIKE '%@%'","id = ?"),array($member['pid']))) !== null)
                        {
                            $Files = $this->db->executeQuery("SELECT s.id, f.uuid, s.ctime FROM tl_member_secureDownloads s INNER JOIN tl_files f ON f.uuid = s.uuid WHERE s.pid = ? AND s.nc_sent != '1' ORDER BY s.ctime DESC, f.name",[$Member->id]);
                            $arrFileIds = [];

                            if ($Files->rowCount())
                            {
                                $arrDownloads = array();
                                foreach ($Files->iterateAssociative() as $file)
                                {
                                    if (($File = FilesModel::findByUuid($file['uuid'])) !== null)
                                    {
                                        $arrDownloads[] = date('d.m.Y', $file['ctime']) . " - " . $File->name;
                                        $arrFileIds[] = $file['id'];
                                    }
                                }

                                $Notification->send(array
                                (
                                    'member_email' => $Member->email,
                                    'member_firstname' => $Member->firstname,
                                    'member_lastname' => $Member->lastname,
                                    'downloads' => implode("\n",$arrDownloads),
                                    'downloads_html' => "<ul>\n<li>".implode("</li>\n<li>",$arrDownloads)."</li>\n</ul>"
                                ),$GLOBALS['TL_LANGUAGE']);
                            }

                            // Flag news as sent
                            $this->db->executeStatement("UPDATE tl_member_secureDownloads SET nc_sent = '1' WHERE id IN (" . implode(',', $arrFileIds) . ")");
                        }
                    }
                    $this->logger->log(LogLevel::INFO, 'Secure downloads notifications has been sent (' . $Members->rowCount() . ')', array('contao' => new ContaoContext(__METHOD__, 'CRON')));
                }
            }
        }
    }

    /**
     * @CronJob("* * * * *")
     */
    public function importFiles(string $scope)
    {
        $projectDir = System::getContainer()->getParameter('kernel.project_dir');
        $Configurations = MemberfilesConfigModel::findAll();

        $intFileCount = 0;

        // No root pages available
        if ($Configurations === null)
        {
            return;
        }

        // Iterate page roots
        while ($Configurations->next())
        {
            if (System::getContainer()->getParameter('kernel.debug'))
            {
                $this->logger->log(LogLevel::DEBUG, sprintf('Starting secure downloads import for %s', $Configurations->title), array('contao' => new ContaoContext(__METHOD__, 'CRON')));
            }

            // Continue if secure downloads is disabled
            if (!$Configurations->enabled)
            {
                continue;
            }

            // Get folder models
            $Source = FilesModel::findByUuid($Configurations->source);
            $Target = FilesModel::findByUuid($Configurations->target);

            // Continue if folder doesn't exist
            if ($Source === null || !file_exists($projectDir . "/".$Source->path) || $Target === null || !file_exists($projectDir . "/".$Target->path))
            {
                $this->logger->log(LogLevel::WARNING, sprintf('Source or target folder is missing for page root %s. %s %s', $Configurations->title,$Source->path,$Target->path), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
                continue;
            }

            // Read files inside folder
            $Files = FilesModel::findByPid($Source->uuid);

            // Continue if folder is empty
            if ($Files === null)
            {
                continue;
            }

            // Escape single quotes in fragment regexp
            $Configurations->regexp = addcslashes($Configurations->regexp,'\'');

            // Iterate files
            while ($Files->next())
            {
                // Skip subfolders and special files
                if ($Files->type == 'folder' || strpos($Files->name,'.') === 0)
                {
                    continue;
                }

                // Continue if file doesn't exist
                if (!file_exists($projectDir . "/".$Files->path))
                {
                    continue;
                }

                $objFile = new File($Files->path);

                // Check for fragments
                if (!preg_match('/'.$Configurations->regexp.'/U',$objFile->filename,$fragments))
                {
                    $this->logger->log(LogLevel::WARNING, sprintf('File "%s" is missing a member fragment or has the wrong syntax.', $objFile->name), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
                    continue;
                }

                // Find member
                $arrColumns = array();
                $arrValues = array();
                foreach (StringUtil::deserialize($Configurations->fields, true) as $i => $field)
                {
                    $arrColumns[] = "$field = ?";
                    $arrValues[] = $fragments[$i+1];
                }
                $objMember = MemberModel::findOneBy($arrColumns,$arrValues);

                // Continue if no member found
                if ($objMember === null) {
                    $this->logger->log(LogLevel::WARNING, sprintf('Could not find member for file "%s". (%s) [%s]', $objFile->name, implode(' AND ',$arrColumns),implode(', ',$arrValues)), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
                    continue;
                }

                // Remove fragments from file name
                $baseFilename = preg_replace('/'.$Configurations->regexp.'/U','',$objFile->filename);
                $sanitizedFilename = $baseFilename.'.'.$objFile->extension;

                // Check or create folder structure
                $strSubFolder = sprintf("%03d",ceil(($objMember->id+1)/100)-1).'/'.$objMember->id;
                $strTargetFolder = $Target->path.'/'.$strSubFolder;

                $arrFolders = explode('/',$strTargetFolder);
                $strStartPath = '';

                foreach ($arrFolders as $folder)
                {
                    if (!is_dir($projectDir . '/' . $strStartPath . $folder))
                    {
                        Files::getInstance()->mkdir($strStartPath . $folder);
                    }
                    $strStartPath .= $folder . '/';
                }

                // Do not overwrite existing files
                $copyId = 1;
                while(file_exists($projectDir . "/".$strTargetFolder.'/'.$sanitizedFilename))
                {
                    $sanitizedFilename = $baseFilename.'_'.$copyId++.'.'.$objFile->extension;
                }

                // Try to copy file
                if (!Files::getInstance()->copy($objFile->path,$strTargetFolder.'/'.$sanitizedFilename))
                {
                    $this->logger->log(LogLevel::WARNING, sprintf('Could not import file "%s" for member ID "%s".', $objFile->name, $objMember->id), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
                    continue;
                }

                $File = Dbafs::addResource($strTargetFolder.'/'.$sanitizedFilename,false);

                // Add file to DB and delete source
                $arrData = array(
                    'pid'     => $objMember->id,
                    'uuid'    => ($File !== null ? $File->uuid : ''),
                    'tstamp'  => time(),
                    'ctime'   => time(),
//          'name'    => $sanitizedFilename,
//          'path'    => $strTargetFolder.'/'.$sanitizedFilename
                );
                $this->db->insert('tl_member_secureDownloads',$arrData);

                if (!$objFile->delete()) {
                    $this->logger->log(LogLevel::WARNING, sprintf('Could not delete source file "%s" for member ID "%s".', $objFile->name, $objMember->id), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
                }

                $intFileCount++;
            }
        }

        if ($intFileCount)
        {
            $this->logger->log(LogLevel::INFO, sprintf("Imported %s files for secure member downloads",$intFileCount), array('contao' => new ContaoContext(__METHOD__, 'CRON')));
        }
    }
}