<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Migration;

use Contao\CoreBundle\Migration\AbstractMigration;
use Contao\CoreBundle\Migration\MigrationResult;
use Doctrine\DBAL\Connection;
use Ferienpass\CoreBundle\Message\AccountActivated;

/**
 * Backfills User.activatedAt for rows created before the column existed.
 *
 * Every path that enables an account now stamps the column, but existing rows
 * have NULL — and NULL is exactly what {@see \Ferienpass\CoreBundle\Cron\TruncateUnactivatedUsers}
 * reads as "abandoned registration". Without this backfill, suspending a
 * long-standing account would delete it four days later.
 *
 * Runs after the schema diff (contao:migrate) has added the column.
 */
class UserActivatedAtMigration extends AbstractMigration
{
    public function __construct(private readonly Connection $db)
    {
    }

    public function shouldRun(): bool
    {
        $schemaManager = $this->db->createSchemaManager();

        if (!$schemaManager->tablesExist(['User'])) {
            return false;
        }

        if (!isset($schemaManager->listTableColumns('User')['activatedat'])) {
            // Column not created yet — let the schema diff run first.
            return false;
        }

        // Only report as pending when a row exists that run() can actually
        // resolve. A suspended account without an AccountActivated log entry
        // stays NULL by design (below) — counting it here would keep this
        // migration pending forever, and contao:migrate loops until nothing is
        // pending, so it would re-run this no-op on every round.
        $enabledPending = (bool) $this->db
            ->executeQuery('SELECT 1 FROM `User` WHERE activatedAt IS NULL AND disable = 0 LIMIT 1')
            ->fetchOne()
        ;

        if ($enabledPending) {
            return true;
        }

        if (!$schemaManager->tablesExist(['UserMessengerLog', 'MessengerLog'])) {
            return false;
        }

        return (bool) $this->db->executeQuery(
            'SELECT 1
                FROM `User` u
                INNER JOIN UserMessengerLog uml ON uml.user_id = u.id
                INNER JOIN MessengerLog l ON l.id = uml.log_id
                WHERE u.activatedAt IS NULL AND u.disable = 1 AND l.message = :message
                LIMIT 1',
            ['message' => AccountActivated::class]
        )->fetchOne();
    }

    public function run(): MigrationResult
    {
        // Anything currently enabled is activated by definition — an account
        // cannot reach that state without having been activated.
        $enabled = $this->db->executeStatement(
            'UPDATE `User` SET activatedAt = createdAt WHERE activatedAt IS NULL AND disable = 0'
        );

        // Accounts an admin already suspended: the notification log is the only
        // record of their activation. That is the signal the cron read on every
        // run until now — applied here once, so the runtime query no longer
        // depends on whether the "Konto aktiviert" mail was ever sent.
        $suspended = 0;
        if ($this->db->createSchemaManager()->tablesExist(['UserMessengerLog', 'MessengerLog'])) {
            $suspended = $this->db->executeStatement(
                'UPDATE `User` u
                    INNER JOIN (
                        SELECT uml.user_id AS user_id, MIN(l.createdAt) AS activatedAt
                        FROM UserMessengerLog uml
                        INNER JOIN MessengerLog l ON l.id = uml.log_id
                        WHERE l.message = :message
                        GROUP BY uml.user_id
                    ) act ON act.user_id = u.id
                    SET u.activatedAt = act.activatedAt
                    WHERE u.activatedAt IS NULL',
                ['message' => AccountActivated::class]
            );
        }

        return $this->createResult(true, \sprintf('Backfilled User.activatedAt for %d enabled and %d suspended account(s).', $enabled, $suspended));
    }
}
