<?php

declare(strict_types=1);

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;
use RuntimeException;

final class EnsureSchoolYearOnFinancialTables extends Migration
{
    /** @var array<string, bool> table => nullable */
    private array $tables = [
        'discount_usages'            => false,
        'event_charges'              => true,
        'invoice_event'              => true,
        'payment_error'              => true,
        'payment_notification_logs'  => true,
        'payment_transactions'       => true,
        'reimbursement_batch_items'  => true,
    ];

    public function up(): void
    {
        if ($this->db->DBDriver !== 'MySQLi') {
            throw new RuntimeException('This migration requires MySQL/MariaDB through the MySQLi driver.');
        }

        foreach ($this->tables as $table => $nullable) {
            if (! $this->db->tableExists($table)) {
                continue;
            }

            if (! $this->db->fieldExists('school_year', $table)) {
                $this->forge->addColumn($table, [
                    'school_year' => [
                        'type'       => 'VARCHAR',
                        'constraint' => 9,
                        'null'       => $nullable,
                        'after'      => $this->afterColumn($table),
                    ],
                ]);
            }

            $this->backfillSchoolYear($table);
            $this->assertValidYears($table);

            $nullSql = $nullable ? 'NULL' : 'NOT NULL';
            $this->db->query(
                sprintf(
                    'ALTER TABLE `%s` MODIFY `school_year` VARCHAR(9) %s',
                    str_replace('`', '``', $table),
                    $nullSql
                )
            );

            $this->ensureIndex($table);
        }
    }

    public function down(): void
    {
        // These columns are part of the business model and must not be dropped on rollback.
    }

    private function assertValidYears(string $table): void
    {
        $sql = sprintf(
            "SELECT COUNT(*) AS invalid_count
             FROM `%s`
             WHERE `school_year` IS NOT NULL
               AND (
                    `school_year` NOT REGEXP '^[0-9]{4}-[0-9]{4}$'
                    OR CAST(RIGHT(`school_year`, 4) AS UNSIGNED)
                       <> CAST(LEFT(`school_year`, 4) AS UNSIGNED) + 1
               )",
            str_replace('`', '``', $table)
        );

        $row = $this->db->query($sql)->getRowArray();
        $invalid = (int) ($row['invalid_count'] ?? 0);

        if ($invalid > 0) {
            throw new RuntimeException(
                "Cannot normalize {$table}.school_year: {$invalid} invalid value(s). Expected YYYY-YYYY, for example 2025-2026."
            );
        }
    }

    private function backfillSchoolYear(string $table): void
    {
        if ($table === 'discount_usages' && $this->db->tableExists('invoices')) {
            $this->db->query(
                "UPDATE `discount_usages` du
                 INNER JOIN `invoices` i ON i.`id` = du.`invoice_id`
                 SET du.`school_year` = i.`school_year`
                 WHERE (du.`school_year` IS NULL OR TRIM(du.`school_year`) = '')
                   AND i.`school_year` REGEXP '^[0-9]{4}-[0-9]{4}$'
                   AND CAST(RIGHT(i.`school_year`, 4) AS UNSIGNED)
                       = CAST(LEFT(i.`school_year`, 4) AS UNSIGNED) + 1"
            );
        }
    }

    private function ensureIndex(string $table): void
    {
        $indexName = 'idx_' . $table . '_school_year';
        if (strlen($indexName) > 64) {
            $indexName = 'idx_' . substr(hash('sha256', $table . '_school_year'), 0, 24);
        }

        $row = $this->db->query(
            'SELECT COUNT(*) AS index_count
             FROM information_schema.STATISTICS
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = ?
               AND COLUMN_NAME = ?',
            [$table, 'school_year']
        )->getRowArray();

        if ((int) ($row['index_count'] ?? 0) === 0) {
            $this->db->query(sprintf(
                'CREATE INDEX `%s` ON `%s` (`school_year`)',
                str_replace('`', '``', $indexName),
                str_replace('`', '``', $table)
            ));
        }
    }

    private function afterColumn(string $table): string
    {
        $preferred = ['semester', 'invoice_id', 'payment_id', 'batch_id'];
        foreach ($preferred as $column) {
            if ($this->db->fieldExists($column, $table)) {
                return $column;
            }
        }

        return 'id';
    }
}
