<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * Phase 6 of the tenant-marking series: payments, the root of the financial
 * chain. payment_logs, financial_breakdowns and secretary_earnings inherit the
 * environment through their foreign keys and get no column of their own.
 *
 * The environment of a payment is the receiving side, never the payer:
 *
 *   appointment  → the environment of the appointment (marked in phase 2)
 *   subscription → the environment the buyer owns (doctor first, then clinic —
 *                  the same order PaymentManager::handleSubscriptionActivation
 *                  uses to create the subscription itself)
 *   sms_wallet   → the environment already recorded in the payment metadata
 *
 * clinic_subscriptions cannot drive the subscription backfill: it links to a
 * payment (payment_id) rather than to a user, and trial rows carry no payment at
 * all, so a payment whose subscription was never activated has no row to join.
 *
 * Statements run through $this->connection rather than addSql() because the
 * NOT NULL guard has to sit between the backfill and the tightening; addSql()
 * defers everything to the end of up().
 */
final class Version20260728140000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Mark payments with the environment that receives them';
    }

    public function up(Schema $schema): void
    {
        $this->connection->executeStatement(
            'ALTER TABLE payments ADD entity_type VARCHAR(10) NULL, ADD entity_id INT NULL'
        );

        // Appointment payments: the appointment already carries the pair.
        $this->connection->executeStatement(
            'UPDATE payments p JOIN appointments a ON a.id = p.appointment_id
             SET p.entity_type = a.entity_type, p.entity_id = a.entity_id
             WHERE p.appointment_id IS NOT NULL'
        );

        // SMS wallet charges: the environment was stored in the metadata when the
        // charge was started, which is also what PaymentManager reads on callback.
        $this->connection->executeStatement(
            "UPDATE payments
             SET entity_type = JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.entity_type')),
                 entity_id   = JSON_EXTRACT(metadata, '$.entity_id')
             WHERE entity_type IS NULL
               AND type = 'sms_wallet'
               AND JSON_EXTRACT(metadata, '$.entity_id') IS NOT NULL"
        );

        // Subscription payments: the environment the payer owns.
        $this->connection->executeStatement(
            "UPDATE payments p JOIN doctors d ON d.user_id = p.user_id
             SET p.entity_type = 'doctor', p.entity_id = d.id
             WHERE p.entity_type IS NULL"
        );
        $this->connection->executeStatement(
            "UPDATE payments p JOIN clinics c ON c.user_id = p.user_id
             SET p.entity_type = 'clinic', p.entity_id = c.id
             WHERE p.entity_type IS NULL"
        );

        // A payment left without an environment is a kind this analysis has not
        // seen. Guessing one would put real money in the wrong ledger.
        $remaining = (int) $this->connection->fetchOne(
            'SELECT COUNT(*) FROM payments WHERE entity_type IS NULL OR entity_id IS NULL'
        );
        $this->abortIf(
            $remaining > 0,
            "Backfill left {$remaining} payments without an environment; classify them by hand before rerunning."
        );

        $this->connection->executeStatement(
            'ALTER TABLE payments MODIFY entity_type VARCHAR(10) NOT NULL, MODIFY entity_id INT NOT NULL'
        );
        $this->connection->executeStatement(
            'CREATE INDEX idx_payments_entity_date ON payments (entity_type, entity_id, created_at)'
        );
    }

    public function down(Schema $schema): void
    {
        $this->addSql('DROP INDEX idx_payments_entity_date ON payments');
        $this->addSql('ALTER TABLE payments 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;
    }
}
