<?php

declare(strict_types=1);

namespace App\Service\Cron\Job;

use App\Service\Cron\BaseCronJob;
use App\Service\Utils\DatesUtils;
use Doctrine\DBAL\Connection;
use JetBrains\PhpStorm\Pure;

/**
 * Cronjob that delete records older than N days in DB tables like Audit_ or Message.
 *
 * >>> ot:cron run clean-db --preview
 * >>> ot:cron run clean-db
 */
class CleanDb extends BaseCronJob
{
    /**
     * Delay before deleting the record from the DB.
     */
    private const PURGE_RECORDS_OLDER_THAN = 60;
    private const PURGE_AUDIT_RECORDS_OLDER_THAN = 180;

    #[Pure]
    public function __construct(
        private Connection $connection,
    ) {
        parent::__construct();
    }

    /**
     * Preview the result of the execution, without actually deleting anything.
     *
     * @throws \Exception
     */
    public function preview(): void
    {
        $this->purgeDb(false);
    }

    /**
     * Proceed to the deletion of the files and the purge of the DB.
     *
     * @throws \Exception
     */
    public function execute(): void
    {
        $this->purgeDb();
    }

    /**
     * Purge the DB from temporary file records older than N days.
     *
     * @throws \Exception
     * @throws \Doctrine\DBAL\Driver\Exception
     * @throws \Throwable
     */
    protected function purgeDb(bool $commit = true): void
    {
        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P'.self::PURGE_RECORDS_OLDER_THAN.'D'));

        $maxDateAudit = DatesUtils::new();
        $maxDateAudit->sub(new \DateInterval('P'.self::PURGE_AUDIT_RECORDS_OLDER_THAN.'D'));

        $this->connection->beginTransaction();
        $this->connection->setAutoCommit(false);

        $purged = 0;

        try {
            $purged += $this->purgeAuditTables($maxDateAudit);
            $purged += $this->purgeMessages($maxDate);
            $purged += $this->purgeNotifications($maxDate);

            if ($commit) {
                $this->connection->commit();
                $this->logger->info('DB purged - '.$purged.' records permanently deleted');

                return;
            } else {
                $this->connection->rollback();
                $this->ui->print('DB purged - '.$purged.' records would be permanently deleted');
            }
        } catch (\Throwable $exception) {
            $this->connection->rollback();
            throw $exception;
        }
    }

    /**
     * Purge Access tables and returns the number of deleted records.
     *
     * @throws \Doctrine\DBAL\Driver\Exception
     */
    protected function purgeAuditTables(\DateTime $maxDate): int
    {
        $this->logger->info('Purge Audit_* tables from the records created before the '.$maxDate->format('c'));

        $tableNames = $this->connection->createSchemaManager()->listTableNames();

        $total = 0;

        foreach (array_values($tableNames) as $tableName) {
            if (preg_match("/Audit_\w+/", $tableName)) {
                /** @noinspection SqlWithoutWhere */
                $sql = "DELETE a, r 
                     FROM opentalent.{$tableName} a
                     INNER JOIN opentalent.revisions r ON r.id = a.rev
                     WHERE r.timestamp < :maxDate;";

                $stmt = $this->connection->prepare($sql);
                $purged = $stmt->executeStatement(['maxDate' => $maxDate->format('Y-m-d')]);

                $this->logger->debug('* '.$tableName.' : '.$purged.' lines to delete');

                $total += $purged;
            }
        }

        return $total;
    }

    /**
     * Purge Message and ReportMessage tables and returns the number of deleted records.
     *
     * @throws \Doctrine\DBAL\Exception
     */
    protected function purgeMessages(\DateTime $maxDate): int
    {
        $this->logger->info('Purge the DB from the messages created before the '.$maxDate->format('c'));

        $sql = 'DELETE r
                FROM opentalent.Message m
                inner join opentalent.ReportMessage r on r.message_id = m.id
                where (m.dateSent < :maxDate or (m.dateSent is null and m.createDate < :maxDate)) and m.isSystem = true and m.id > 0;';

        $stmt = $this->connection->prepare($sql);
        $purgedReportMessage = $stmt->executeStatement(['maxDate' => $maxDate->format('Y-m-d')]);

        $this->logger->debug('* ReportMessage : '.$purgedReportMessage.' lines to delete');

        $sql = 'DELETE
                FROM opentalent.Message
                where (dateSent < :maxDate or (dateSent is null and createDate < :maxDate)) and isSystem = true and id > 0;';

        $stmt = $this->connection->prepare($sql);
        $purgedMessage = $stmt->executeStatement(['maxDate' => $maxDate->format('Y-m-d')]);

        $this->logger->debug('* Message : '.$purgedMessage.' lines to delete');

        return $purgedReportMessage + $purgedMessage;
    }

    /**
     * Purge Information (Notification Discr) and NotificationUser tables, and returns the number of deleted records.
     *
     * @throws \Doctrine\DBAL\Exception
     */
    protected function purgeNotifications(\DateTime $maxDate): int
    {
        $this->logger->info('Purge the DB from the notifications created before the '.$maxDate->format('Y-m-d'));

        $sql = "DELETE u
                FROM opentalent.Information i
                inner join opentalent.NotificationUser u on u.notification_id = i.id
                where i.createDate < :maxDate and i.discr = 'notification';";

        $stmt = $this->connection->prepare($sql);
        $purgedNotificationUser = $stmt->executeStatement(['maxDate' => $maxDate->format('Y-m-d')]);

        $this->logger->debug('* NotificationUser : '.$purgedNotificationUser.' lines to delete');

        $sql = "DELETE
                FROM opentalent.Information
                where createDate < :maxDate and discr = 'notification';";

        $stmt = $this->connection->prepare($sql);
        $purgedNotification = $stmt->executeStatement(['maxDate' => $maxDate->format('Y-m-d')]);

        $this->logger->debug('* Information : '.$purgedNotification.' lines to delete');

        return $purgedNotificationUser + $purgedNotification;
    }
}
