<?php

namespace App\Commands;

use App\Libraries\InvoiceLedgerService;
use App\Models\InvoiceModel;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;

class RecalculateInvoices extends BaseCommand
{
    protected $group = 'Financial';
    protected $name = 'financial:recalculate-invoices';
    protected $description = 'Audits invoice totals against the centralized ledger and optionally writes the corrected values.';
    protected $usage = 'financial:recalculate-invoices [--commit] [--invoice-id=123] [--parent-id=456] [--school-year=2025-2026] [--semester=Fall]';
    protected $options = [
        '--commit' => 'Persist recalculated totals. Without this flag the command runs in dry-run mode.',
        '--invoice-id' => 'Only process a single invoice ID.',
        '--parent-id' => 'Only process invoices for one parent.',
        '--school-year' => 'Only process invoices for the specified school year.',
        '--semester' => 'Only process invoices for the specified semester.',
    ];

    public function run(array $params)
    {
        $options = $this->parseOptions($params);
        $invoiceModel = new InvoiceModel();
        $ledgerService = new InvoiceLedgerService();
        $invoiceIds = $this->loadInvoiceIds($invoiceModel, $options);

        if ($invoiceIds === []) {
            CLI::write('No invoices matched the provided filters.', 'yellow');
            return;
        }

        $commit = !empty($options['commit']);
        CLI::write(($commit ? 'Applying' : 'Dry-run auditing') . ' ' . count($invoiceIds) . ' invoice(s)...', 'yellow');

        $changed = 0;
        $unchanged = 0;
        $errors = 0;

        foreach ($invoiceIds as $invoiceId) {
            $invoice = $invoiceModel->find($invoiceId);
            if (!$invoice) {
                continue;
            }

            try {
                $calculation = $ledgerService->calculateInvoice($invoiceId);
                $diffs = $this->diffInvoice($invoice, $calculation);

                if ($diffs === []) {
                    $unchanged++;
                    CLI::write('Invoice #' . $invoiceId . ' unchanged.', 'green');
                    continue;
                }

                $changed++;
                CLI::write('Invoice #' . $invoiceId . ' requires recalculation:', 'light_yellow');
                foreach ($diffs as $label => $values) {
                    CLI::write(sprintf(
                        '  %s: %s -> %s',
                        $label,
                        (string) $values['from'],
                        (string) $values['to']
                    ));
                }

                if ($commit) {
                    $ledgerService->recalculateInvoice($invoiceId);
                    CLI::write('  saved', 'blue');
                }
            } catch (\Throwable $e) {
                $errors++;
                CLI::error('Invoice #' . $invoiceId . ' failed: ' . $e->getMessage());
            }
        }

        CLI::newLine();
        CLI::write('Processed: ' . count($invoiceIds), 'white');
        CLI::write('Changed: ' . $changed, $changed > 0 ? 'yellow' : 'white');
        CLI::write('Unchanged: ' . $unchanged, 'green');
        CLI::write('Errors: ' . $errors, $errors > 0 ? 'red' : 'white');

        if (!$commit) {
            CLI::write('Dry-run complete. Re-run with --commit to persist corrections.', 'light_blue');
        }
    }

    protected function parseOptions(array $params): array
    {
        $options = [
            'commit' => false,
            'invoice-id' => null,
            'parent-id' => null,
            'school-year' => null,
            'semester' => null,
        ];

        foreach ($params as $param) {
            $value = trim((string) $param);
            if ($value === '--commit') {
                $options['commit'] = true;
                continue;
            }

            if (!str_starts_with($value, '--') || !str_contains($value, '=')) {
                continue;
            }

            [$key, $raw] = explode('=', substr($value, 2), 2);
            if (array_key_exists($key, $options)) {
                $options[$key] = $raw;
            }
        }

        return $options;
    }

    protected function loadInvoiceIds(InvoiceModel $invoiceModel, array $options): array
    {
        $builder = $invoiceModel->select('id')->orderBy('id', 'ASC');

        if (!empty($options['invoice-id'])) {
            $builder->where('id', (int) $options['invoice-id']);
        }

        if (!empty($options['parent-id'])) {
            $builder->where('parent_id', (int) $options['parent-id']);
        }

        if (!empty($options['school-year'])) {
            $builder->where('school_year', (string) $options['school-year']);
        }

        if (!empty($options['semester'])) {
            $builder->where('semester', (string) $options['semester']);
        }

        return array_map(
            static fn (array $row): int => (int) ($row['id'] ?? 0),
            $builder->findAll()
        );
    }

    protected function diffInvoice(array $invoice, array $calculation): array
    {
        $fields = [
            'total_amount' => 'total_amount',
            'paid_amount' => 'paid_amount',
            'balance' => 'balance',
            'status' => 'status',
            'has_discount' => 'has_discount',
        ];
        $diffs = [];

        foreach ($fields as $invoiceKey => $calcKey) {
            $current = (string) ($invoice[$invoiceKey] ?? '');
            $recalculated = (string) ($calculation[$calcKey] ?? '');
            if ($current !== $recalculated) {
                $diffs[$invoiceKey] = [
                    'from' => $current,
                    'to' => $recalculated,
                ];
            }
        }

        return $diffs;
    }
}
