<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class FinancialSystemLedgerCleanup extends Migration
{
    public function up()
    {
        $this->addInstallmentSequenceColumn();
        $this->backfillInstallmentSequence();
        $this->ensurePaymentDateHasTime();
        $this->ensureIndexes();
        $this->repairPaymentInvoiceTerms();
        $this->normalizePaymentStatuses();
        $this->ensureConfigurationDefaults();
        $this->archivePaypalTables();
        $this->refreshFinancialNavItems();
    }

    public function down()
    {
        $this->restorePaypalTables();

        if ($this->db->tableExists('payments') && $this->db->fieldExists('installment_seq', 'payments')) {
            $this->forge->dropColumn('payments', 'installment_seq');
        }

        $this->dropIndexIfExists('payments', 'idx_payments_invoice_id');
        $this->dropIndexIfExists('payments', 'idx_payments_parent_year');
        $this->dropIndexIfExists('payments', 'idx_payments_parent_year_semester');
        $this->dropIndexIfExists('payments', 'uniq_payments_transaction_id');
        $this->dropIndexIfExists('invoices', 'idx_invoices_parent_year_semester');
        $this->dropIndexIfExists('discount_usages', 'idx_discount_usages_invoice_id');
        $this->dropIndexIfExists('discount_usages', 'idx_discount_usages_parent_year_semester');
        $this->dropIndexIfExists('refunds', 'idx_refunds_invoice_id');
        $this->dropIndexIfExists('refunds', 'idx_refunds_parent_year_semester_status');
        $this->dropIndexIfExists('additional_charges', 'idx_additional_charges_invoice_id');
        $this->dropIndexIfExists('additional_charges', 'idx_additional_charges_parent_year_semester_status');
        $this->dropIndexIfExists('invoice_event', 'idx_invoice_event_invoice_id');

        if ($this->db->tableExists('nav_items')) {
            $forecastRow = $this->db->table('nav_items')
                ->select('id')
                ->where('url', 'administrator/tuition-forecast')
                ->get()
                ->getRowArray();

            if ($forecastRow && $this->db->tableExists('role_nav_items')) {
                $this->db->table('role_nav_items')
                    ->where('nav_item_id', (int) $forecastRow['id'])
                    ->delete();
            }

            $this->db->table('nav_items')
                ->where('url', 'administrator/tuition-forecast')
                ->delete();
        }
    }

    protected function addInstallmentSequenceColumn(): void
    {
        if ($this->db->tableExists('payments') && !$this->db->fieldExists('installment_seq', 'payments')) {
            $this->forge->addColumn('payments', [
                'installment_seq' => [
                    'type' => 'INT',
                    'constraint' => 11,
                    'null' => true,
                    'after' => 'number_of_installments',
                ],
            ]);
        }
    }

    protected function backfillInstallmentSequence(): void
    {
        if (!$this->db->tableExists('payments') || !$this->db->fieldExists('installment_seq', 'payments')) {
            return;
        }

        $this->db->query(
            'UPDATE `payments`
                SET `installment_seq` = `number_of_installments`
              WHERE `installment_seq` IS NULL'
        );
    }

    protected function ensurePaymentDateHasTime(): void
    {
        if (!$this->db->tableExists('payments') || !$this->db->fieldExists('payment_date', 'payments')) {
            return;
        }

        $this->db->query('ALTER TABLE `payments` MODIFY `payment_date` DATETIME NOT NULL');
    }

    protected function ensureIndexes(): void
    {
        $this->addIndexIfMissing('payments', 'idx_payments_invoice_id', ['invoice_id']);
        $this->dropIndexIfExists('payments', 'idx_payments_parent_year_semester');
        $this->addIndexIfMissing('payments', 'idx_payments_parent_year', ['parent_id', 'school_year']);
        $this->addIndexIfMissing('invoices', 'idx_invoices_parent_year_semester', ['parent_id', 'school_year', 'semester']);
        $this->addIndexIfMissing('discount_usages', 'idx_discount_usages_invoice_id', ['invoice_id']);
        $this->addIndexIfMissing('discount_usages', 'idx_discount_usages_parent_year_semester', ['parent_id', 'school_year', 'semester']);
        $this->addIndexIfMissing('refunds', 'idx_refunds_invoice_id', ['invoice_id']);
        $this->addIndexIfMissing('refunds', 'idx_refunds_parent_year_semester_status', ['parent_id', 'school_year', 'semester', 'status']);
        $this->addIndexIfMissing('additional_charges', 'idx_additional_charges_invoice_id', ['invoice_id']);
        $this->addIndexIfMissing('additional_charges', 'idx_additional_charges_parent_year_semester_status', ['parent_id', 'school_year', 'semester', 'status']);
        $this->addIndexIfMissing('invoice_event', 'idx_invoice_event_invoice_id', ['invoice_id']);

        if ($this->db->tableExists('payments') && $this->db->fieldExists('transaction_id', 'payments') && !$this->hasDuplicateTransactionIds()) {
            $this->addIndexIfMissing('payments', 'uniq_payments_transaction_id', ['transaction_id'], true);
        }
    }

    protected function repairPaymentInvoiceTerms(): void
    {
        if (!$this->db->tableExists('payments') || !$this->db->tableExists('invoices')) {
            return;
        }

        $this->db->query(
            'UPDATE `payments` p
                JOIN `invoices` i ON i.`id` = p.`invoice_id`
                 SET p.`school_year` = i.`school_year`
               WHERE COALESCE(p.`school_year`, \'\') <> COALESCE(i.`school_year`, \'\')'
        );
    }

    protected function normalizePaymentStatuses(): void
    {
        if (!$this->db->tableExists('payments') || !$this->db->fieldExists('status', 'payments')) {
            return;
        }

        $this->db->query(
            "UPDATE `payments`
                SET `status` = 'recorded'
              WHERE LOWER(TRIM(`status`)) IN ('paid', 'partially paid', 'payment recorded', 'full', 'completed')"
        );
    }

    protected function ensureConfigurationDefaults(): void
    {
        if (!$this->db->tableExists('configuration')) {
            return;
        }

        $defaults = [
            'tuition_calculator_version' => 'old',
            'youth_fee' => '200.00',
            'new_tuition_full_amount' => '370.00',
            'new_tuition_youth_amount' => '200.00',
            'new_tuition_second_student_discount' => '50.00',
            'new_tuition_third_student_discount' => '50.00',
            'new_tuition_fourth_plus_discount' => '100.00',
        ];

        foreach ($defaults as $key => $value) {
            $row = $this->db->table('configuration')
                ->select('id, config_value')
                ->where('config_key', $key)
                ->orderBy('id', 'ASC')
                ->get()
                ->getRowArray();

            if (!$row) {
                $this->db->table('configuration')->insert([
                    'config_key' => $key,
                    'config_value' => $value,
                ]);
                continue;
            }

            if (trim((string) ($row['config_value'] ?? '')) === '') {
                $this->db->table('configuration')
                    ->where('config_key', $key)
                    ->update(['config_value' => $value]);
            }
        }
    }

    protected function archivePaypalTables(): void
    {
        // Keep legacy PayPal tables under their original names. Renaming them
        // during migrate makes restored data appear to disappear from the app.
    }

    protected function restorePaypalTables(): void
    {
        $this->renameTableIfPresent('archived_paypal_payments', 'paypal_payments');
        $this->renameTableIfPresent('archived_paypal_transactions', 'paypal_transactions');
    }

    protected function refreshFinancialNavItems(): void
    {
        if (!$this->db->tableExists('nav_items')) {
            return;
        }

        $parentColumn = null;
        if ($this->db->fieldExists('menu_parent_id', 'nav_items')) {
            $parentColumn = 'menu_parent_id';
        } elseif ($this->db->fieldExists('parent_id', 'nav_items')) {
            $parentColumn = 'parent_id';
        }

        if ($parentColumn === null) {
            return;
        }

        $this->db->table('nav_items')
            ->groupStart()
                ->where('url', 'administrator/paypal_transactions')
                ->orWhere('url', 'admin/paypal-transactions')
                ->orWhere('label', 'PaypalTransactions')
                ->orWhere('label', 'PayPal Transactions')
            ->groupEnd()
            ->delete();

        $financialRow = $this->db->table('nav_items')
            ->select('id')
            ->where('label', 'Financial')
            ->where($parentColumn, null)
            ->get()
            ->getRowArray();

        if (!$financialRow) {
            return;
        }

        $forecastUrl = 'administrator/tuition-forecast';
        $existing = $this->db->table('nav_items')
            ->select('id')
            ->where('url', $forecastUrl)
            ->get()
            ->getRowArray();

        if ($existing) {
            $forecastId = (int) $existing['id'];
        } else {
            $this->db->table('nav_items')->insert([
                $parentColumn => (int) $financialRow['id'],
                'label' => 'Tuition Forecast',
                'url' => $forecastUrl,
                'sort_order' => 4,
                'is_enabled' => 1,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ]);
            $forecastId = (int) $this->db->insertID();
        }

        if ($forecastId <= 0 || !$this->db->tableExists('role_nav_items') || !$this->db->tableExists('roles')) {
            return;
        }

        $roles = $this->db->table('roles')
            ->select('id')
            ->whereIn('name', ['administrator', 'administrative staff', 'principal', 'vice_principal', 'head of department (finance)'])
            ->get()
            ->getResultArray();

        foreach ($roles as $role) {
            $roleId = (int) ($role['id'] ?? 0);
            if ($roleId <= 0) {
                continue;
            }

            $exists = $this->db->table('role_nav_items')
                ->where('role_id', $roleId)
                ->where('nav_item_id', $forecastId)
                ->get()
                ->getRowArray();

            if ($exists) {
                continue;
            }

            $this->db->table('role_nav_items')->insert([
                'role_id' => $roleId,
                'nav_item_id' => $forecastId,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ]);
        }
    }

    protected function renameTableIfPresent(string $from, string $to): void
    {
        if (!$this->db->tableExists($from) || $this->db->tableExists($to)) {
            return;
        }

        $this->db->query(sprintf('RENAME TABLE `%s` TO `%s`', $from, $to));
    }

    protected function addIndexIfMissing(string $table, string $indexName, array $columns, bool $unique = false): void
    {
        if (!$this->db->tableExists($table) || $this->indexExists($table, $indexName)) {
            return;
        }

        $quotedColumns = implode(', ', array_map(static fn (string $column): string => '`' . $column . '`', $columns));
        $type = $unique ? 'UNIQUE INDEX' : 'INDEX';
        $sql = sprintf('ALTER TABLE `%s` ADD %s `%s` (%s)', $table, $type, $indexName, $quotedColumns);
        $this->db->query($sql);
    }

    protected function dropIndexIfExists(string $table, string $indexName): void
    {
        if (!$this->db->tableExists($table) || !$this->indexExists($table, $indexName)) {
            return;
        }

        $this->db->query(sprintf('ALTER TABLE `%s` DROP INDEX `%s`', $table, $indexName));
    }

    protected function indexExists(string $table, string $indexName): bool
    {
        if (!$this->db->tableExists($table)) {
            return false;
        }

        $rows = $this->db->query(sprintf('SHOW INDEX FROM `%s`', $table))->getResultArray();
        foreach ($rows as $row) {
            if (($row['Key_name'] ?? null) === $indexName) {
                return true;
            }
        }

        return false;
    }

    protected function hasDuplicateTransactionIds(): bool
    {
        $row = $this->db->table('payments')
            ->select('transaction_id')
            ->where('transaction_id IS NOT NULL', null, false)
            ->where('transaction_id !=', '')
            ->groupBy('transaction_id')
            ->having('COUNT(*) >', 1, false)
            ->get(1)
            ->getRowArray();

        return $row !== null;
    }
}
