<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * Phase 2 of the tenant-marking series: store the owning environment explicitly
 * on the booking tables as the (entity_type, entity_id) pair already used by
 * service_sections, patient_records and clinic_staff.
 *
 * Statements run through $this->connection instead of addSql() because the
 * backfill has to happen between adding the nullable columns and tightening
 * them to NOT NULL — addSql() defers everything to the end of up(), which would
 * run the guard before the data exists.
 *
 * MariaDB commits implicitly on DDL, so this migration cannot roll back as a
 * unit. The order below is what keeps it safe: the replacement unique index is
 * created before the old one is dropped, so the tables are never left without
 * uniqueness protection.
 */
final class Version20260728074119 extends AbstractMigration
{
    /** clinic wins when set; NULL means the doctor's own practice. */
    private const BACKFILL = "SET entity_type = IF(clinic_id IS NULL, 'doctor', 'clinic'),
                                  entity_id   = IFNULL(clinic_id, doctor_id)";

    public function getDescription(): string
    {
        return 'Mark appointments, weekly_schedules and date_overrides with their owning tenant';
    }

    public function up(Schema $schema): void
    {
        foreach (['appointments', 'weekly_schedules', 'date_overrides'] as $table) {
            $this->connection->executeStatement(
                "ALTER TABLE {$table} ADD entity_type VARCHAR(10) NULL, ADD entity_id INT NULL"
            );
            $this->connection->executeStatement("UPDATE {$table} " . self::BACKFILL);

            // Defensive: a clinic_id pointing at a deleted clinic falls back to
            // the doctor's own practice rather than aborting the migration.
            $this->connection->executeStatement(
                "UPDATE {$table} t LEFT JOIN clinics c ON c.id = t.clinic_id
                 SET t.entity_type = 'doctor', t.entity_id = t.doctor_id
                 WHERE t.clinic_id IS NOT NULL AND c.id IS NULL"
            );

            $remaining = (int) $this->connection->fetchOne(
                "SELECT COUNT(*) FROM {$table} WHERE entity_type IS NULL OR entity_id IS NULL"
            );
            $this->abortIf($remaining > 0, "Backfill left {$remaining} rows in {$table} without a tenant.");

            $this->connection->executeStatement(
                "ALTER TABLE {$table} MODIFY entity_type VARCHAR(10) NOT NULL, MODIFY entity_id INT NOT NULL"
            );
        }

        $this->connection->executeStatement(
            'CREATE INDEX idx_appointments_tenant_slot ON appointments (entity_type, entity_id, slot_start)'
        );
        $this->connection->executeStatement(
            'CREATE INDEX idx_appointments_tenant_status ON appointments (entity_type, entity_id, status)'
        );

        // New unique key first, old one and its generated column afterwards.
        $this->connection->executeStatement(
            'CREATE UNIQUE INDEX uniq_weekly_schedule_doctor_tenant ON weekly_schedules (doctor_id, entity_type, entity_id)'
        );
        $this->connection->executeStatement('DROP INDEX idx_weekly_schedules_doctor_clinic ON weekly_schedules');
        $this->connection->executeStatement('ALTER TABLE weekly_schedules DROP clinic_key');

        $this->connection->executeStatement(
            'CREATE UNIQUE INDEX uniq_date_override_doctor_tenant_date ON date_overrides (doctor_id, entity_type, entity_id, date)'
        );
        $this->connection->executeStatement('DROP INDEX uniq_date_override_doctor_clinic_date ON date_overrides');
        $this->connection->executeStatement('ALTER TABLE date_overrides DROP clinic_key');
    }

    public function down(Schema $schema): void
    {
        $this->addSql("ALTER TABLE weekly_schedules ADD clinic_key INT AS (IFNULL(clinic_id, 0)) STORED");
        $this->addSql('CREATE UNIQUE INDEX idx_weekly_schedules_doctor_clinic ON weekly_schedules (doctor_id, clinic_key)');
        $this->addSql('DROP INDEX uniq_weekly_schedule_doctor_tenant ON weekly_schedules');

        $this->addSql("ALTER TABLE date_overrides ADD clinic_key INT AS (IFNULL(clinic_id, 0)) STORED");
        $this->addSql('CREATE UNIQUE INDEX uniq_date_override_doctor_clinic_date ON date_overrides (doctor_id, clinic_key, date)');
        $this->addSql('DROP INDEX uniq_date_override_doctor_tenant_date ON date_overrides');

        $this->addSql('DROP INDEX idx_appointments_tenant_slot ON appointments');
        $this->addSql('DROP INDEX idx_appointments_tenant_status ON appointments');

        foreach (['appointments', 'weekly_schedules', 'date_overrides'] as $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;
    }
}
