<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Phase 6 of the tenant-marking series: bank accounts and POS devices move from
 * the user to the environment. A doctor who runs both a private practice and a
 * clinic keeps separate card readers for each, so the owning environment — not
 * the person who registered the device — is what a payment method belongs to.
 *
 * user_id stays: it still records who registered the device.
 *
 * Unlike every other tenant table these two columns stay NULLABLE on purpose.
 * Nothing in the existing data says which of a multi-environment owner's cards
 * belongs to which environment, and guessing would point real money at the wrong
 * account. Those rows are left unassigned for the owner to resolve, and the
 * migration reports how many there are so the number is visible in the deploy
 * output rather than discovered later.
 *
 * Consequence, deliberate and documented in docs/architecture/tenancy.md: an
 * unassigned row is invisible in every environment, because TenantFilter compares
 * for equality and NULL equals nothing. The owner reaches it through the
 * "unassigned" list, which is read outside the filter and scoped by user_id.
 */
final class Version20260728141500 extends AbstractMigration
{
    private const TABLES = ['bank_accounts', 'pos_devices'];

    public function getDescription(): string
    {
        return 'Move bank accounts and POS devices from their registering user to an environment';
    }

    public function up(Schema $schema): void
    {
        foreach (self::TABLES as $table) {
            $this->connection->executeStatement(
                "ALTER TABLE {$table} ADD entity_type VARCHAR(10) NULL, ADD entity_id INT NULL"
            );

            // Only owners with exactly one environment can be resolved without a guess.
            $this->connection->executeStatement(
                "UPDATE {$table} t
                 JOIN (SELECT u.id AS user_id,
                              MAX(d.id) AS doctor_id,
                              MAX(c.id) AS clinic_id,
                              COUNT(DISTINCT d.id) + COUNT(DISTINCT c.id) AS envs
                       FROM users u
                       LEFT JOIN doctors d ON d.user_id = u.id
                       LEFT JOIN clinics c ON c.user_id = u.id
                       GROUP BY u.id) x ON x.user_id = t.user_id
                 SET t.entity_type = IF(x.clinic_id IS NOT NULL, 'clinic', 'doctor'),
                     t.entity_id   = IFNULL(x.clinic_id, x.doctor_id)
                 WHERE x.envs = 1"
            );

            $unassigned = (int) $this->connection->fetchOne(
                "SELECT COUNT(*) FROM {$table} WHERE entity_type IS NULL"
            );
            $this->write(sprintf(
                '  %s: %d row(s) left without an environment — their owner has more than one and must choose.',
                $table,
                $unassigned,
            ));

            $this->connection->executeStatement(
                "CREATE INDEX idx_{$table}_entity ON {$table} (entity_type, entity_id)"
            );
        }
    }

    public function down(Schema $schema): void
    {
        foreach (self::TABLES as $table) {
            $this->addSql("DROP INDEX idx_{$table}_entity ON {$table}");
            $this->addSql("ALTER TABLE {$table} DROP entity_type, DROP entity_id");
        }
    }

    /** DDL on MariaDB commits implicitly; wrapping up() in a transaction would be a lie. */
    public function isTransactional(): bool
    {
        return false;
    }
}
