<?php

declare(strict_types=1);

namespace WhmcsMigrate\Phases;

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

final class Phase3Services extends AbstractPhase
{
    public function getPhaseNumber(): int
    {
        return 3;
    }

    public function getName(): string
    {
        return 'Services → Subscriptions + Services';
    }

    public function validate(): bool
    {
        if ($this->isExportOnly()) {
            // Export needs client mappings to know which clients to fetch services for
            $clientCount = $this->state->getMappingCount('clients');

            if ($clientCount === 0) {
                // In export-only mode, we can export by iterating all clients from the clients export
                if (! $this->exportManager->hasExport('clients')) {
                    $this->logger->error('Validation failed: no client export or mappings found — run Phase 1 export first');

                    return false;
                }
            }

            return true;
        }

        // Import modes need client + product mappings
        $clientCount = $this->state->getMappingCount('clients');

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

            return false;
        }

        $productCount = $this->state->getMappingCount('products');

        if ($productCount === 0) {
            $this->logger->error('Validation failed: no product mappings found — run Phase 2 first');

            return false;
        }

        $this->logger->info("Validation passed: {$clientCount} client mappings, {$productCount} product mappings");

        return true;
    }

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

            return;
        }

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

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

            return;
        }

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

            return;
        }

        $this->runFromApi();
    }

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

        // Get client IDs from existing mappings or from the clients export
        $clientIds = [];

        $clientMappings = $this->state->getAllMappings('clients');

        if (! empty($clientMappings)) {
            $clientIds = array_map('intval', array_keys($clientMappings));
        } else {
            // Read from clients export
            foreach ($this->exportManager->readRecords('clients') as $client) {
                $id = (int) ($client['id'] ?? 0);

                if ($id > 0) {
                    $clientIds[] = $id;
                }
            }
        }

        $totalClients = count($clientIds);
        $this->logger->info("Exporting services for {$totalClients} clients");

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

        $tracker = new ProgressTracker('Exporting services', $totalClients);
        $exported = 0;

        foreach ($clientIds as $whmcsClientId) {
            try {
                $response = $this->api->call('GetClientsProducts', ['clientid' => $whmcsClientId]);

                if (($response['result'] ?? '') === 'success') {
                    $products = $response['products']['product'] ?? [];

                    foreach ($products as $product) {
                        $product['_whmcs_client_id'] = $whmcsClientId;
                        $this->exportManager->writeRecord('client_services', $product);
                        $exported++;
                    }
                }
            } catch (Throwable $e) {
                $this->logger->warning("Failed to fetch services for client {$whmcsClientId}: {$e->getMessage()}");
            }

            $tracker->advance();
        }

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

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

            return;
        }

        $this->markStarted();

        $totalServices = $this->exportManager->countRecords('client_services');
        $this->logger->info("Found {$totalServices} service records in export");

        if ($totalServices === 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 services', $totalServices);
        $tracker->setCurrent($processedCount);

        $currentIndex = 0;

        foreach ($this->exportManager->readRecords('client_services') as $product) {
            $currentIndex++;

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

            $whmcsServiceId = (int) ($product['id'] ?? 0);
            $whmcsClientId = (int) ($product['_whmcs_client_id'] ?? 0);
            $ezUserId = $this->state->getMapping('clients', $whmcsClientId);

            if ($whmcsServiceId === 0 || $whmcsClientId === 0 || $ezUserId === null) {
                $errors++;
                $processedCount++;
                $tracker->advance();

                continue;
            }

            try {
                if ($this->migrateService($whmcsServiceId, $whmcsClientId, $ezUserId, $product)) {
                    $count++;
                }
            } catch (Throwable $e) {
                $errors++;
                $this->logger->error("Failed to migrate service {$whmcsServiceId}", [
                    '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 3 complete: {$count} services migrated, {$errors} errors (elapsed: {$tracker->getElapsed()})");
    }

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

        $clientMappings = $this->state->getAllMappings('clients');
        $totalClients = count($clientMappings);

        $this->logger->info("Processing services for {$totalClients} mapped clients");

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

            return;
        }

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

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

        $clientEntries = array_values(
            array_map(
                fn (string $whmcsId, int $ezUserId): array => [
                    'whmcs_id' => (int) $whmcsId,
                    'ez_user_id' => $ezUserId,
                ],
                array_keys($clientMappings),
                array_values($clientMappings),
            ),
        );

        $tracker = new ProgressTracker('Migrating services', $totalClients);
        $tracker->setCurrent($startOffset);

        for ($i = $startOffset; $i < $totalClients; $i++) {
            $whmcsClientId = $clientEntries[$i]['whmcs_id'];
            $ezUserId = $clientEntries[$i]['ez_user_id'];

            try {
                $result = $this->migrateClientServices($whmcsClientId, $ezUserId);
                $count += $result['migrated'];
                $errors += $result['errors'];
            } catch (Throwable $e) {
                $errors++;
                $this->logger->error("Failed to process services for WHMCS client {$whmcsClientId}", [
                    'error' => $e->getMessage(),
                ]);
            }

            $this->state->setProgress($this->getPhaseKey(), 'running', $i + 1, $count, $errors);
            $this->state->save();
            $tracker->advance();

            if (($i + 1) % 50 === 0 || $i + 1 === $totalClients) {
                $this->logger->info("Progress: client " . ($i + 1) . "/{$totalClients}, {$count} services migrated, {$errors} errors");
            }
        }

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

    /**
     * Migrate all services/products for a single WHMCS client.
     *
     * @return array{migrated: int, errors: int}
     */
    private function migrateClientServices(int $whmcsClientId, int $ezUserId): array
    {
        $response = $this->api->call('GetClientsProducts', ['clientid' => $whmcsClientId]);

        if (($response['result'] ?? '') !== 'success') {
            $this->logger->warning("GetClientsProducts failed for client {$whmcsClientId}", [
                'response' => $response,
            ]);

            return ['migrated' => 0, 'errors' => 1];
        }

        $products = $response['products']['product'] ?? [];
        $migrated = 0;
        $errors = 0;

        foreach ($products as $product) {
            $whmcsServiceId = (int) ($product['id'] ?? 0);

            if ($whmcsServiceId === 0) {
                $this->logger->warning('Skipping service with no ID for client ' . $whmcsClientId);
                $errors++;

                continue;
            }

            try {
                if ($this->migrateService($whmcsServiceId, $whmcsClientId, $ezUserId, $product)) {
                    $migrated++;
                }
            } catch (Throwable $e) {
                $errors++;
                $this->logger->error("Failed to migrate service {$whmcsServiceId} for client {$whmcsClientId}", [
                    'error' => $e->getMessage(),
                ]);
            }
        }

        return ['migrated' => $migrated, 'errors' => $errors];
    }

    /**
     * Migrate a single WHMCS service/product to EZSCALE subscription + service.
     */
    private function migrateService(int $whmcsServiceId, int $whmcsClientId, int $ezUserId, array $product): bool
    {
        if ($this->state->getMapping('services', $whmcsServiceId) !== null) {
            $this->logger->debug("Service {$whmcsServiceId} already mapped, skipping");

            return false;
        }

        $pid = (int) ($product['pid'] ?? 0);
        $planId = $this->state->getMapping('products', $pid);

        if ($planId === null) {
            $this->logger->warning("No plan mapping for WHMCS product {$pid}, skipping service {$whmcsServiceId}");
            $this->logSkipped('service', "No plan mapping for product {$pid} (service {$whmcsServiceId})");

            return false;
        }

        $plan = $this->db->queryOne(
            "SELECT `slug`, `service_type` FROM `plans` WHERE `id` = :id LIMIT 1",
            ['id' => $planId],
        );

        if ($plan === null) {
            $this->logger->warning("Plan {$planId} not found in database, skipping service {$whmcsServiceId}");
            $this->logSkipped('service', "Plan {$planId} not found in DB (service {$whmcsServiceId})");

            return false;
        }

        $planSlug = (string) $plan['slug'];
        $serviceType = (string) $plan['service_type'];
        $platform = StatusMapper::mapPlatform($serviceType); // null for mysql, backups

        $stripeId = "whmcs_import_{$whmcsServiceId}";

        if ($this->db->tableHasRow('subscriptions', ['stripe_id' => $stripeId])) {
            $this->logger->warning("Subscription with stripe_id '{$stripeId}' already exists, skipping service {$whmcsServiceId}");
            $this->logSkipped('service', "Duplicate stripe_id {$stripeId}");

            return false;
        }

        $whmcsStatus = (string) ($product['status'] ?? 'Active');
        $billingCycle = StatusMapper::mapBillingCycle((string) ($product['billingcycle'] ?? 'Monthly'));
        $gateway = StatusMapper::mapGateway((string) ($product['paymentmethod'] ?? 'stripe'));
        $stripeStatus = StatusMapper::mapStripeStatus($whmcsStatus);
        $serviceStatus = StatusMapper::mapServiceStatus($whmcsStatus);

        $regDate = $this->parseDate((string) ($product['regdate'] ?? ''));
        $nextDueDate = $this->parseDate((string) ($product['nextduedate'] ?? ''));
        $now = date('Y-m-d H:i:s');

        $isActive = strtolower($whmcsStatus) === 'active';
        $isSuspended = strtolower($whmcsStatus) === 'suspended';
        $isTerminated = in_array(strtolower($whmcsStatus), ['terminated', 'cancelled'], true);

        $recurringAmount = $this->nullIfEmpty((string) ($product['recurringamount'] ?? ''));
        $dedicatedIp = $this->nullIfEmpty((string) ($product['dedicatedip'] ?? ''));
        $domain = $this->nullIfEmpty((string) ($product['domain'] ?? ''));

        if ($this->isDryRun()) {
            $this->logger->info("[DRY RUN] Would create subscription + service for WHMCS service {$whmcsServiceId} (client {$whmcsClientId}, plan {$planSlug}, status {$whmcsStatus})");

            return true;
        }

        $this->db->beginTransaction();

        try {
            $subscriptionId = $this->db->insert('subscriptions', [
                'user_id' => $ezUserId,
                'type' => $planSlug,
                'plan_id' => $planId,
                'gateway' => $gateway,
                'stripe_id' => $stripeId,
                'stripe_status' => $stripeStatus,
                'stripe_price' => null,
                'quantity' => 1,
                'billing_cycle' => $billingCycle,
                'recurring_amount' => $recurringAmount,
                'current_period_end' => $nextDueDate,
                'created_at' => $regDate ?? $now,
                'updated_at' => $now,
            ]);

            $this->db->insert('subscription_items', [
                'subscription_id' => $subscriptionId,
                'stripe_id' => "whmcs_si_{$whmcsServiceId}",
                'stripe_product' => "whmcs_prod_{$pid}",
                'stripe_price' => "whmcs_price_{$pid}",
                'quantity' => 1,
            ]);

            $serviceId = $this->db->insert('services', [
                'user_id' => $ezUserId,
                'subscription_id' => $subscriptionId,
                'plan_id' => $planId,
                'service_type' => $serviceType,
                'platform' => $platform,
                'status' => $serviceStatus,
                'ipv4_address' => $dedicatedIp,
                'domain' => $domain,
                'credentials' => $this->encryptor->encryptArray([]),
                'provisioned_at' => $isActive ? ($regDate ?? $now) : null,
                'suspended_at' => $isSuspended ? $now : null,
                'terminated_at' => $isTerminated ? $now : null,
                'auto_renew' => $isActive ? 1 : 0,
                'created_at' => $regDate ?? $now,
                'updated_at' => $now,
            ]);

            $this->db->commit();

            $this->state->setMapping('services', $whmcsServiceId, $serviceId);
            $this->state->setMapping('subscriptions', $whmcsServiceId, $subscriptionId);

            $this->logger->debug("Migrated service {$whmcsServiceId} → subscription {$subscriptionId} + service {$serviceId} (plan: {$planSlug}, status: {$serviceStatus})");

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

            throw $e;
        }
    }

    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);
    }
}
