<?php

declare(strict_types=1);

namespace WhmcsMigrate\Phases;

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

final class Phase1Clients extends AbstractPhase
{
    public function getPhaseNumber(): int
    {
        return 1;
    }

    public function getName(): string
    {
        return 'Clients → Users + UserProfiles';
    }

    public function validate(): bool
    {
        // In export-only mode, skip DB validation
        if ($this->isExportOnly()) {
            return $this->validateApiConnectivity();
        }

        // In from-export mode, skip API validation
        if ($this->isFromExport()) {
            return $this->validateDbPrerequisites();
        }

        // Normal mode: validate both
        return $this->validateDbPrerequisites() && $this->validateApiConnectivity();
    }

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

            return;
        }

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

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

            return;
        }

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

            return;
        }

        $this->runFromApi();
    }

    /**
     * Export all client data from WHMCS API to JSONL files.
     */
    private function runExport(): void
    {
        $this->logger->info('Exporting clients from WHMCS API...');

        // Reset the export files for this entity
        $this->exportManager->resetExport('clients');
        $this->exportManager->resetExport('client_details');

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

        $this->logger->info("Found {$totalClients} clients in WHMCS");

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

        $batchSize = $this->getBatchSize();
        $offset = 0;
        $exported = 0;
        $progress = new ProgressTracker('Exporting clients', $totalClients);

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

            $clients = $response['clients']['client'] ?? [];

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

            // Export each client's basic info from the list
            $this->exportManager->writeBatch('clients', $clients);

            // Fetch and export full details for each client
            foreach ($clients as $client) {
                $whmcsId = (int) ($client['id'] ?? 0);

                if ($whmcsId === 0) {
                    continue;
                }

                try {
                    $details = $this->api->call('GetClientsDetails', ['clientid' => $whmcsId]);

                    if (($details['result'] ?? '') === 'success') {
                        // Merge the datecreated from the list response
                        $details['_datecreated_from_list'] = (string) ($client['datecreated'] ?? '');
                        $this->exportManager->writeRecord('client_details', $details);
                    }
                } catch (Throwable $e) {
                    $this->logger->warning("Failed to fetch details for client {$whmcsId}: {$e->getMessage()}");
                }

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

            $offset += count($clients);
        }

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

    /**
     * Import clients from previously exported JSONL files.
     */
    private function runFromExport(): void
    {
        if (! $this->exportManager->hasExport('client_details')) {
            $this->logger->error('No client_details export found. Run --export-only first.');

            return;
        }

        $this->markStarted();

        $totalClients = $this->exportManager->countRecords('client_details');
        $this->logger->info("Found {$totalClients} client records in export");

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

            return;
        }

        // Look up the customer role_id once
        $role = $this->db->queryOne(
            "SELECT `id` FROM `roles` WHERE `name` = :name AND `guard_name` = :guard",
            ['name' => 'customer', 'guard' => 'web'],
        );

        $customerRoleId = (int) $role['id'];

        $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 clients', $totalClients);
        $tracker->setCurrent($processedCount);

        $currentIndex = 0;

        foreach ($this->exportManager->readRecords('client_details') as $details) {
            $currentIndex++;

            // Skip already-processed records
            if ($currentIndex <= $processedCount) {
                continue;
            }

            $whmcsId = (int) ($details['id'] ?? $details['userid'] ?? $details['client_id'] ?? 0);

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

                continue;
            }

            try {
                $dateCreatedFromList = (string) ($details['_datecreated_from_list'] ?? '');
                $result = $this->migrateClientFromDetails($whmcsId, $customerRoleId, $details, $dateCreatedFromList);

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

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

            // Save progress every 100 records
            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 1 complete: {$count} clients migrated, {$errors} errors (elapsed: {$tracker->getElapsed()})");
    }

    /**
     * Original behavior: fetch from API and import directly.
     */
    private function runFromApi(): void
    {
        $this->markStarted();

        // Look up the customer role_id once
        $role = $this->db->queryOne(
            "SELECT `id` FROM `roles` WHERE `name` = :name AND `guard_name` = :guard",
            ['name' => 'customer', 'guard' => 'web'],
        );

        $customerRoleId = (int) $role['id'];

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

        $this->logger->info("Found {$totalClients} clients in WHMCS");

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

            return;
        }

        // Resume from saved offset if applicable
        $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 clients', $totalClients);
        $tracker->setCurrent($offset);

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

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

            $clients = $response['clients']['client'] ?? [];

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

                break;
            }

            foreach ($clients as $client) {
                $whmcsId = (int) ($client['id'] ?? 0);

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

                    continue;
                }

                try {
                    // datecreated is in GetClients response, not GetClientsDetails
                    $dateCreatedFromList = (string) ($client['datecreated'] ?? '');
                    $result = $this->migrateClient($whmcsId, $customerRoleId, $dateCreatedFromList);

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

                $tracker->advance();
            }

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

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

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

    /**
     * Migrate a single client from API (fetches GetClientsDetails).
     */
    private function migrateClient(int $whmcsId, int $customerRoleId, string $dateCreatedFromList = ''): bool
    {
        if ($this->state->getMapping('clients', $whmcsId) !== null) {
            $this->logger->debug("Client {$whmcsId} already mapped, skipping");

            return false;
        }

        $details = $this->api->call('GetClientsDetails', ['clientid' => $whmcsId]);

        if (($details['result'] ?? '') !== 'success') {
            $this->logger->warning("GetClientsDetails failed for client {$whmcsId}", [
                'response' => $details,
            ]);
            $this->logSkipped('client', "API returned non-success for client {$whmcsId}");

            return false;
        }

        return $this->migrateClientFromDetails($whmcsId, $customerRoleId, $details, $dateCreatedFromList);
    }

    /**
     * Migrate a single client from already-fetched detail data.
     * Used by both API and from-export modes.
     */
    private function migrateClientFromDetails(int $whmcsId, int $customerRoleId, array $details, string $dateCreatedFromList = ''): bool
    {
        if ($this->state->getMapping('clients', $whmcsId) !== null) {
            $this->logger->debug("Client {$whmcsId} already mapped, skipping");

            return false;
        }

        $email = trim(strtolower($details['email'] ?? ''));

        if ($email === '') {
            $this->logger->warning("Client {$whmcsId} has no email, skipping");
            $this->logSkipped('client', "No email for client {$whmcsId}");

            return false;
        }

        $existingUser = $this->db->queryOne(
            "SELECT `id` FROM `users` WHERE `email` = :email LIMIT 1",
            ['email' => $email],
        );

        if ($existingUser !== null) {
            $existingUserId = (int) $existingUser['id'];
            $this->logger->warning("Client {$whmcsId} email '{$email}' already exists as user {$existingUserId}, mapping to existing user");
            $this->state->setMapping('clients', $whmcsId, $existingUserId);

            return false;
        }

        $firstname = trim((string) ($details['firstname'] ?? ''));
        $lastname = trim((string) ($details['lastname'] ?? ''));
        $name = trim("{$firstname} {$lastname}");

        if ($name === '') {
            $name = strstr($email, '@', true) ?: $email;
        }

        $status = StatusMapper::mapClientStatus((string) ($details['status'] ?? 'Active'));
        $phone = $this->nullIfEmpty((string) ($details['phonenumber'] ?? ''));
        $company = $this->nullIfEmpty((string) ($details['companyname'] ?? ''));
        $dateCreated = $this->parseDate($dateCreatedFromList) ?? $this->parseDate((string) ($details['datecreated'] ?? ''));
        $credit = (string) ($details['credit'] ?? '0.00');
        $now = date('Y-m-d H:i:s');

        $stripeCustomerId = null;
        $pmType = null;
        $pmLastFour = null;
        $gatewayIdRaw = (string) ($details['gatewayid'] ?? '');

        if ($gatewayIdRaw !== '') {
            $gatewayData = json_decode($gatewayIdRaw, true);

            if (is_array($gatewayData) && isset($gatewayData['customer']) && str_starts_with($gatewayData['customer'], 'cus_')) {
                $stripeCustomerId = $gatewayData['customer'];
            }
        }

        $cctype = $this->nullIfEmpty((string) ($details['cctype'] ?? ''));
        $cclastfour = $this->nullIfEmpty((string) ($details['cclastfour'] ?? ''));

        if ($cctype !== null) {
            $pmType = strtolower($cctype);
        }

        if ($cclastfour !== null && strlen($cclastfour) <= 4) {
            $pmLastFour = $cclastfour;
        }

        $adminNotes = "Imported from WHMCS. Client ID: {$whmcsId}";

        if ((float) $credit > 0) {
            $adminNotes .= ". Credit balance: \${$credit}";
        }

        if ($this->isDryRun()) {
            $stripeInfo = $stripeCustomerId ? " (Stripe: {$stripeCustomerId})" : '';
            $this->logger->info("[DRY RUN] Would create user for client {$whmcsId}: {$name} <{$email}>{$stripeInfo}");

            return true;
        }

        $this->db->beginTransaction();

        try {
            $userId = $this->db->insert('users', [
                'name' => $name,
                'email' => $email,
                'password' => password_hash(bin2hex(random_bytes(32)), PASSWORD_BCRYPT),
                'status' => $status,
                'phone' => $phone,
                'company' => $company,
                'admin_notes' => $adminNotes,
                'stripe_id' => $stripeCustomerId,
                'pm_type' => $pmType,
                'pm_last_four' => $pmLastFour,
                'email_verified_at' => $now,
                'created_at' => $dateCreated ?? $now,
                'updated_at' => $now,
            ]);

            $this->db->insert('user_profiles', [
                'user_id' => $userId,
                'billing_address_line1' => $this->nullIfEmpty((string) ($details['address1'] ?? '')),
                'billing_address_line2' => $this->nullIfEmpty((string) ($details['address2'] ?? '')),
                'billing_city' => $this->nullIfEmpty((string) ($details['city'] ?? '')),
                'billing_state' => $this->nullIfEmpty((string) ($details['state'] ?? '')),
                'billing_zip' => $this->nullIfEmpty((string) ($details['postcode'] ?? '')),
                'billing_country' => $this->nullIfEmpty((string) ($details['country'] ?? '')),
                'company_name' => $company,
            ]);

            $this->db->execute(
                "INSERT IGNORE INTO `model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES (:role_id, :model_type, :model_id)",
                ['role_id' => $customerRoleId, 'model_type' => 'App\\Models\\User', 'model_id' => $userId],
            );

            $this->db->commit();

            $this->state->setMapping('clients', $whmcsId, $userId);

            $this->logger->debug("Migrated client {$whmcsId} → user {$userId} ({$name} <{$email}>)");

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

            throw $e;
        }
    }

    private function validateDbPrerequisites(): bool
    {
        $role = $this->db->queryOne(
            "SELECT `id` FROM `roles` WHERE `name` = :name AND `guard_name` = :guard",
            ['name' => 'customer', 'guard' => 'web'],
        );

        if ($role === null) {
            $this->logger->error('Validation failed: no "customer" role found in roles table (guard_name=web)');

            return false;
        }

        return true;
    }

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

            if (($response['result'] ?? '') !== 'success') {
                $this->logger->error('Validation failed: WHMCS GetClients 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);
    }
}
