<?php

declare(strict_types=1);

namespace WhmcsMigrate\Phases;

use Throwable;
use WhmcsMigrate\ProgressTracker;
use WhmcsMigrate\StatusMapper;

final class Phase4Invoices extends AbstractPhase
{
    public function getPhaseNumber(): int
    {
        return 4;
    }

    public function getName(): string
    {
        return 'Invoices → Invoices + InvoiceItems';
    }

    public function validate(): bool
    {
        if ($this->isExportOnly()) {
            return $this->validateApi();
        }

        if ($this->isFromExport()) {
            return $this->validateDb();
        }

        return $this->validateDb() && $this->validateApi();
    }

    public function run(): void
    {
        if (! $this->isExportOnly() && $this->shouldSkip()) {
            $this->logger->info('Phase 4 already complete, skipping');

            return;
        }

        $this->logger->section('Phase 4: ' . $this->getName());

        if ($this->isExportOnly()) {
            $this->runExport();

            return;
        }

        if ($this->isFromExport()) {
            $this->runFromExport();

            return;
        }

        $this->runFromApi();
    }

    private function runExport(): void
    {
        $this->logger->info('Exporting invoices from WHMCS API...');
        $this->exportManager->resetExport('invoices');
        $this->exportManager->resetExport('invoice_details');

        // Get total invoice count
        $probeResponse = $this->api->call('GetInvoices', ['limitstart' => 0, 'limitnum' => 1]);
        $totalInvoices = (int) ($probeResponse['totalresults'] ?? 0);

        $this->logger->info("Found {$totalInvoices} invoices in WHMCS");

        if ($totalInvoices === 0) {
            return;
        }

        $batchSize = $this->getBatchSize();
        $offset = 0;
        $exported = 0;
        $tracker = new ProgressTracker('Exporting invoices', $totalInvoices);

        while ($offset < $totalInvoices) {
            $response = $this->api->call('GetInvoices', [
                'limitstart' => $offset,
                'limitnum' => $batchSize,
            ]);

            $invoices = $response['invoices']['invoice'] ?? [];

            if (empty($invoices)) {
                break;
            }

            // Export summary data
            $this->exportManager->writeBatch('invoices', $invoices);

            // Fetch and export detail data (line items) for each invoice
            foreach ($invoices as $invoice) {
                $invoiceId = (int) ($invoice['id'] ?? 0);

                if ($invoiceId === 0) {
                    $tracker->advance();

                    continue;
                }

                try {
                    $detailResponse = $this->api->call('GetInvoice', ['invoiceid' => $invoiceId]);

                    if (($detailResponse['result'] ?? '') === 'success') {
                        // Merge summary fields into detail for a complete record
                        $detailResponse['_summary'] = $invoice;
                        $this->exportManager->writeRecord('invoice_details', $detailResponse);
                    }
                } catch (Throwable $e) {
                    $this->logger->warning("Failed to fetch details for invoice {$invoiceId}: {$e->getMessage()}");
                }

                $exported++;
                $tracker->advance();
            }

            $offset += count($invoices);
        }

        $tracker->finish();
        $this->logger->info("Exported {$exported} invoice records to JSONL");
    }

    private function runFromExport(): void
    {
        if (! $this->exportManager->hasExport('invoices')) {
            $this->logger->error('No invoices export found. Run --export-only first.');

            return;
        }

        $this->markStarted();

        // We need both invoices (summary) and invoice_details (line items)
        // If invoice_details exists, use it (has line items). Otherwise fall back to invoices.
        $hasDetails = $this->exportManager->hasExport('invoice_details');
        $entityName = $hasDetails ? 'invoice_details' : 'invoices';

        $totalInvoices = $this->exportManager->countRecords($entityName);
        $this->logger->info("Found {$totalInvoices} invoice records in export" . ($hasDetails ? ' (with details)' : ' (summary only)'));

        if ($totalInvoices === 0) {
            $this->markComplete(0, 0);

            return;
        }

        $progress = $this->state->getProgress($this->getPhaseKey());
        $processedCount = (int) ($progress['offset'] ?? 0);
        $count = (int) ($progress['count'] ?? 0);
        $errors = (int) ($progress['errors'] ?? 0);

        if ($processedCount > 0) {
            $this->logger->info("Resuming from record {$processedCount} ({$count} migrated, {$errors} errors so far)");
        }

        $tracker = new ProgressTracker('Importing invoices', $totalInvoices);
        $tracker->setCurrent($processedCount);

        $currentIndex = 0;

        foreach ($this->exportManager->readRecords($entityName) as $record) {
            $currentIndex++;

            if ($currentIndex <= $processedCount) {
                continue;
            }

            if ($hasDetails) {
                // invoice_details record has full detail + _summary
                $invoiceData = $record['_summary'] ?? $record;
                $detailData = $record;
                $whmcsInvoiceId = (int) ($invoiceData['id'] ?? $record['invoiceid'] ?? 0);
            } else {
                $invoiceData = $record;
                $detailData = null;
                $whmcsInvoiceId = (int) ($invoiceData['id'] ?? 0);
            }

            if ($whmcsInvoiceId === 0) {
                $errors++;
                $processedCount++;
                $tracker->advance();

                continue;
            }

            try {
                $result = $this->migrateInvoiceFromData($whmcsInvoiceId, $invoiceData, $detailData);

                if ($result) {
                    $count++;
                }
            } catch (Throwable $e) {
                $errors++;
                $this->logger->error("Failed to migrate invoice {$whmcsInvoiceId}", [
                    'error' => $e->getMessage(),
                ]);
            }

            $processedCount++;
            $tracker->advance();

            if ($processedCount % 100 === 0) {
                $this->state->setProgress($this->getPhaseKey(), 'running', $processedCount, $count, $errors);
                $this->state->save();
            }
        }

        $tracker->finish();
        $this->markComplete($count, $errors);
        $this->logger->info("Phase 4 complete: {$count} invoices migrated, {$errors} errors (elapsed: {$tracker->getElapsed()})");
    }

    private function runFromApi(): void
    {
        $this->markStarted();

        // Get total invoice count from WHMCS
        $probeResponse = $this->api->call('GetInvoices', ['limitstart' => 0, 'limitnum' => 1]);
        $totalInvoices = (int) ($probeResponse['totalresults'] ?? 0);

        $this->logger->info("Found {$totalInvoices} invoices in WHMCS");

        if ($totalInvoices === 0) {
            $this->markComplete(0, 0);

            return;
        }

        $progress = $this->state->getProgress($this->getPhaseKey());
        $offset = (int) ($progress['offset'] ?? 0);
        $count = (int) ($progress['count'] ?? 0);
        $errors = (int) ($progress['errors'] ?? 0);
        $batchSize = $this->getBatchSize();

        if ($offset > 0) {
            $this->logger->info("Resuming from offset {$offset} ({$count} migrated, {$errors} errors so far)");
        }

        $tracker = new ProgressTracker('Migrating invoices', $totalInvoices);
        $tracker->setCurrent($offset);

        while ($offset < $totalInvoices) {
            $this->logger->debug("Fetching invoices batch: offset={$offset}, limit={$batchSize}");

            $response = $this->api->call('GetInvoices', [
                'limitstart' => $offset,
                'limitnum' => $batchSize,
            ]);

            $invoices = $response['invoices']['invoice'] ?? [];

            if (empty($invoices)) {
                $this->logger->debug('No more invoices returned, ending pagination');

                break;
            }

            foreach ($invoices as $invoice) {
                $whmcsInvoiceId = (int) ($invoice['id'] ?? 0);

                if ($whmcsInvoiceId === 0) {
                    $this->logger->warning('Skipping invoice with no ID', ['invoice' => $invoice]);
                    $errors++;

                    continue;
                }

                try {
                    $result = $this->migrateInvoice($whmcsInvoiceId, $invoice);

                    if ($result) {
                        $count++;
                    }
                } catch (Throwable $e) {
                    $errors++;
                    $this->logger->error("Failed to migrate invoice {$whmcsInvoiceId}", [
                        'error' => $e->getMessage(),
                    ]);
                }

                $tracker->advance();
            }

            $offset += count($invoices);
            $this->state->setProgress($this->getPhaseKey(), 'running', $offset, $count, $errors);
            $this->state->save();

            $this->logger->info("Batch complete: {$count} migrated, {$errors} errors, offset now {$offset}/{$totalInvoices}");
        }

        $tracker->finish();
        $this->markComplete($count, $errors);
        $this->logger->info("Phase 4 complete: {$count} invoices migrated, {$errors} errors (elapsed: {$tracker->getElapsed()})");
    }

    /**
     * Migrate from API — fetches invoice detail on the fly.
     */
    private function migrateInvoice(int $whmcsInvoiceId, array $invoiceData): bool
    {
        if ($this->state->getMapping('invoices', $whmcsInvoiceId) !== null) {
            $this->logger->debug("Invoice {$whmcsInvoiceId} already mapped, skipping");

            return false;
        }

        // Fetch line items from WHMCS
        $detailResponse = $this->api->call('GetInvoice', ['invoiceid' => $whmcsInvoiceId]);

        if (($detailResponse['result'] ?? '') !== 'success') {
            $this->logger->warning("GetInvoice failed for invoice {$whmcsInvoiceId}", [
                'response' => $detailResponse,
            ]);
            $this->logSkipped('invoice', "GetInvoice API failed for invoice {$whmcsInvoiceId}");

            return false;
        }

        return $this->migrateInvoiceFromData($whmcsInvoiceId, $invoiceData, $detailResponse);
    }

    /**
     * Migrate from already-loaded data (used by both API and export modes).
     */
    private function migrateInvoiceFromData(int $whmcsInvoiceId, array $invoiceData, ?array $detailData): bool
    {
        if ($this->state->getMapping('invoices', $whmcsInvoiceId) !== null) {
            $this->logger->debug("Invoice {$whmcsInvoiceId} already mapped, skipping");

            return false;
        }

        $whmcsUserId = (int) ($invoiceData['userid'] ?? 0);

        if ($whmcsUserId === 0) {
            $this->logger->warning("Invoice {$whmcsInvoiceId} has no userid, skipping");
            $this->logSkipped('invoice', "No userid for invoice {$whmcsInvoiceId}");

            return false;
        }

        $ezscaleUserId = $this->state->getMapping('clients', $whmcsUserId);

        if ($ezscaleUserId === null) {
            $this->logger->warning("Invoice {$whmcsInvoiceId} belongs to unmapped client {$whmcsUserId}, skipping");
            $this->logSkipped('invoice', "Unmapped client {$whmcsUserId} for invoice {$whmcsInvoiceId}");

            return false;
        }

        $gatewayInvoiceId = "whmcs_{$whmcsInvoiceId}";

        if ($this->db->tableHasRow('invoices', ['gateway_invoice_id' => $gatewayInvoiceId])) {
            $this->logger->warning("Invoice with gateway_invoice_id '{$gatewayInvoiceId}' already exists in DB, skipping");
            $this->logSkipped('invoice', "Duplicate gateway_invoice_id {$gatewayInvoiceId}");

            return false;
        }

        $items = [];

        if ($detailData !== null) {
            $items = $detailData['items']['item'] ?? [];
        }

        $status = StatusMapper::mapInvoiceStatus((string) ($invoiceData['status'] ?? 'Unpaid'));
        $gateway = StatusMapper::mapGateway((string) ($invoiceData['paymentmethod'] ?? ''));
        $total = (string) ($invoiceData['total'] ?? '0.00');
        $tax = bcadd(
            (string) ($invoiceData['tax'] ?? '0.00'),
            (string) ($invoiceData['tax2'] ?? '0.00'),
            2,
        );
        $currency = $this->nullIfEmpty((string) ($invoiceData['currencycode'] ?? '')) ?? 'USD';
        $dueDate = $this->parseDate((string) ($invoiceData['duedate'] ?? ''));
        $paidAt = $this->parsePaidAt($invoiceData);
        $createdAt = $this->parseDate((string) ($invoiceData['date'] ?? ''));
        $now = date('Y-m-d H:i:s');

        if ($this->isDryRun()) {
            $this->logger->info("[DRY RUN] Would create invoice for WHMCS invoice {$whmcsInvoiceId}: user={$ezscaleUserId}, total={$total}, status={$status}, items=" . count($items));

            return true;
        }

        $this->db->beginTransaction();

        try {
            $newInvoiceId = $this->db->insert('invoices', [
                'user_id' => $ezscaleUserId,
                'subscription_id' => null,
                'gateway' => $gateway,
                'gateway_invoice_id' => $gatewayInvoiceId,
                'number' => "WHMCS-{$whmcsInvoiceId}",
                'total' => $total,
                'tax' => $tax,
                'currency' => $currency,
                'status' => $status,
                'due_date' => $dueDate,
                'paid_at' => $paidAt,
                'notes' => 'Imported from WHMCS',
                'created_at' => $createdAt ?? $now,
                'updated_at' => $now,
            ]);

            foreach ($items as $item) {
                $description = trim((string) ($item['description'] ?? ''));

                if ($description === '') {
                    $description = 'Invoice item';
                }

                if (mb_strlen($description) > 255) {
                    $description = mb_substr($description, 0, 252) . '...';
                }

                $this->db->insert('invoice_items', [
                    'invoice_id' => $newInvoiceId,
                    'description' => $description,
                    'amount' => (string) ($item['amount'] ?? '0.00'),
                    'quantity' => 1,
                ]);
            }

            $this->db->commit();

            $this->state->setMapping('invoices', $whmcsInvoiceId, $newInvoiceId);

            $this->logger->debug("Migrated invoice {$whmcsInvoiceId} → invoice {$newInvoiceId} (user={$ezscaleUserId}, total={$total}, items=" . count($items) . ')');

            return true;
        } catch (Throwable $e) {
            $this->db->rollback();

            throw $e;
        }
    }

    private function parsePaidAt(array $invoiceData): ?string
    {
        $status = (string) ($invoiceData['status'] ?? '');

        if ($status !== 'Paid') {
            return null;
        }

        $datePaid = trim((string) ($invoiceData['datepaid'] ?? ''));

        if ($datePaid === '' || str_starts_with($datePaid, '0000-00-00')) {
            return null;
        }

        return $this->parseDate($datePaid);
    }

    private function validateDb(): bool
    {
        $result = $this->db->queryOne(
            "SELECT COUNT(*) AS cnt FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table",
            ['table' => 'invoices'],
        );

        if ($result === null || (int) $result['cnt'] === 0) {
            $this->logger->error('Validation failed: "invoices" table does not exist');

            return false;
        }

        $result = $this->db->queryOne(
            "SELECT COUNT(*) AS cnt FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table",
            ['table' => 'invoice_items'],
        );

        if ($result === null || (int) $result['cnt'] === 0) {
            $this->logger->error('Validation failed: "invoice_items" table does not exist');

            return false;
        }

        $clientCount = $this->state->getMappingCount('clients');

        if ($clientCount === 0) {
            $this->logger->error('Validation failed: no client mappings found — Phase 1 must run first');

            return false;
        }

        return true;
    }

    private function validateApi(): bool
    {
        try {
            $response = $this->api->call('GetInvoices', ['limitstart' => 0, 'limitnum' => 1]);

            if (($response['result'] ?? '') !== 'success') {
                $this->logger->error('Validation failed: WHMCS GetInvoices API returned non-success', [
                    'response' => $response,
                ]);

                return false;
            }
        } catch (Throwable $e) {
            $this->logger->error('Validation failed: cannot reach WHMCS API', [
                'error' => $e->getMessage(),
            ]);

            return false;
        }

        return true;
    }

    private function nullIfEmpty(string $value): ?string
    {
        $trimmed = trim($value);

        return $trimmed === '' ? null : $trimmed;
    }

    private function parseDate(string $date): ?string
    {
        $date = trim($date);

        if ($date === '' || str_starts_with($date, '0000-00-00')) {
            return null;
        }

        if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $date)) {
            return $date;
        }

        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            return $date . ' 00:00:00';
        }

        $timestamp = strtotime($date);

        if ($timestamp === false) {
            return null;
        }

        return date('Y-m-d H:i:s', $timestamp);
    }
}
