<?php

declare(strict_types=1);

namespace WhmcsMigrate\Phases;

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

final class Phase2Products extends AbstractPhase
{
    /**
     * WHMCS billing cycle keys mapped to EZSCALE plan_prices billing_cycle values.
     */
    private const array CYCLE_MAP = [
        'monthly' => 'monthly',
        'quarterly' => 'quarterly',
        'semiannually' => 'semi_annual',
        'annually' => 'annual',
    ];

    public function getPhaseNumber(): int
    {
        return 2;
    }

    public function getName(): string
    {
        return 'Products → Plans + PlanPrices';
    }

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

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

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

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

            return;
        }

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

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

            return;
        }

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

            return;
        }

        $this->runFromApi();
    }

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

        $response = $this->api->call('GetProducts');
        $products = $response['products']['product'] ?? [];
        $totalProducts = (int) ($response['totalresults'] ?? count($products));

        $this->logger->info("Found {$totalProducts} products in WHMCS");

        if (empty($products)) {
            return;
        }

        $this->exportManager->writeBatch('products', $products);
        $this->logger->info("Exported {$totalProducts} product records to JSONL");
    }

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

            return;
        }

        $this->markStarted();

        $planData = $this->loadPlanMapping();
        $planMapping = $planData['mappings'];
        $skipList = $planData['skip'];
        $this->logger->info('Plan mapping loaded', ['mapped_products' => count($planMapping), 'skipped_products' => count($skipList)]);

        $products = $this->exportManager->readAll('products');
        $totalProducts = count($products);

        $this->logger->info("Found {$totalProducts} products in export");

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

            return;
        }

        $count = 0;
        $errors = 0;
        $tracker = new ProgressTracker('Importing products', $totalProducts);

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

            if ($pid === 0) {
                $this->logger->warning('Skipping product with no pid', ['product' => $product]);
                $errors++;
                $tracker->advance();

                continue;
            }

            try {
                $result = $this->migrateProduct($pid, $product, $planMapping, $skipList);

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

            $tracker->advance();
        }

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

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

        $planData = $this->loadPlanMapping();
        $planMapping = $planData['mappings'];
        $skipList = $planData['skip'];
        $this->logger->info('Plan mapping loaded', ['mapped_products' => count($planMapping), 'skipped_products' => count($skipList)]);

        $response = $this->api->call('GetProducts');
        $products = $response['products']['product'] ?? [];
        $totalProducts = (int) ($response['totalresults'] ?? count($products));

        $this->logger->info("Found {$totalProducts} products in WHMCS");

        if (empty($products)) {
            $this->markComplete(0, 0);

            return;
        }

        $count = 0;
        $errors = 0;
        $tracker = new ProgressTracker('Migrating products', $totalProducts);

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

            if ($pid === 0) {
                $this->logger->warning('Skipping product with no pid', ['product' => $product]);
                $errors++;
                $tracker->advance();

                continue;
            }

            try {
                $result = $this->migrateProduct($pid, $product, $planMapping, $skipList);

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

            $tracker->advance();
        }

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

    /**
     * Migrate a single WHMCS product to an EZSCALE plan.
     *
     * @param array<string, string> $planMapping  WHMCS pid => EZSCALE slug
     * @param array<int, string>    $skipList      WHMCS pids to skip entirely
     */
    private function migrateProduct(int $pid, array $product, array $planMapping, array $skipList): bool
    {
        // Already mapped — skip
        if ($this->state->getMapping('products', $pid) !== null) {
            $this->logger->debug("Product {$pid} already mapped, skipping");

            return false;
        }

        $productName = (string) ($product['name'] ?? "Product {$pid}");

        // Check if this product is in the explicit skip list
        if (in_array((string) $pid, $skipList, true)) {
            $this->logger->info("Skipping product {$pid} ({$productName}) — in skip list");
            $this->logSkipped('product', "Product {$pid} ({$productName}) is in skip list");

            return false;
        }

        // Check if this product has an explicit mapping to an existing EZSCALE plan
        if (array_key_exists((string) $pid, $planMapping)) {
            return $this->mapToExistingPlan($pid, $productName, $planMapping[(string) $pid]);
        }

        // Unmapped and not skipped — log warning and skip (no auto-creation)
        $this->logger->warning("Product {$pid} ({$productName}) has no mapping and is not in skip list — skipping. Add it to plan_mapping.json mappings or skip list.");
        $this->logSkipped('product', "Product {$pid} ({$productName}) unmapped — not in mappings or skip list");

        return false;
    }

    /**
     * Map a WHMCS product to an existing EZSCALE plan by slug.
     */
    private function mapToExistingPlan(int $pid, string $productName, string $ezscaleSlug): bool
    {
        $plan = $this->db->queryOne(
            "SELECT `id` FROM `plans` WHERE `slug` = :slug LIMIT 1",
            ['slug' => $ezscaleSlug],
        );

        if ($plan === null) {
            $this->logger->error("Mapped plan slug '{$ezscaleSlug}' not found in plans table for product {$pid} ({$productName})");
            $this->logSkipped('product', "Plan slug '{$ezscaleSlug}' not found for WHMCS product {$pid}");

            return false;
        }

        $planId = (int) $plan['id'];
        $this->state->setMapping('products', $pid, $planId);
        $this->logger->info("Mapped WHMCS product {$pid} ({$productName}) → existing plan {$planId} (slug: {$ezscaleSlug})");

        return true;
    }

    /**
     * Auto-create a new EZSCALE plan from WHMCS product data.
     */
    private function autoCreatePlan(int $pid, array $product): bool
    {
        $productName = (string) ($product['name'] ?? "Product {$pid}");
        $groupName = (string) ($product['groupname'] ?? '');
        $description = strip_tags((string) ($product['description'] ?? ''));
        $serviceType = StatusMapper::mapServiceType($groupName, $productName);

        // Extract USD pricing
        $pricing = $product['pricing']['USD'] ?? [];
        $monthlyPrice = $this->parsePrice((string) ($pricing['monthly'] ?? '0.00'));

        // Generate unique slug
        $slug = $this->generateUniqueSlug($productName);

        if ($this->isDryRun()) {
            $this->logger->info("[DRY RUN] Would create plan for product {$pid}: {$productName} (slug: {$slug}, type: {$serviceType}, monthly: {$monthlyPrice})");

            return true;
        }

        // Insert the plan
        $planId = $this->db->insert('plans', [
            'name' => $productName,
            'slug' => $slug,
            'description' => $description !== '' ? $description : null,
            'service_type' => $serviceType,
            'price' => $monthlyPrice,
            'currency' => 'USD',
            'billing_cycle' => 'monthly',
            'status' => 'active',
            'features' => null,
            'provisioning_config' => null,
            'sort_order' => 0,
        ]);

        // Insert plan_prices for each valid billing cycle
        $pricesInserted = $this->insertPlanPrices($planId, $pricing);

        $this->state->setMapping('products', $pid, $planId);
        $this->state->save();

        $this->logger->info("Created plan {$planId} for WHMCS product {$pid}: {$productName} (slug: {$slug}, {$pricesInserted} price tiers)");

        return true;
    }

    /**
     * Insert plan_prices rows for each valid billing cycle.
     *
     * @return int Number of price rows inserted
     */
    private function insertPlanPrices(int $planId, array $pricing): int
    {
        $inserted = 0;

        foreach (self::CYCLE_MAP as $whmcsCycle => $ezscaleCycle) {
            $priceStr = (string) ($pricing[$whmcsCycle] ?? '');
            $price = $this->parsePrice($priceStr);

            // WHMCS uses -1.00 for disabled cycles, skip zero and negative
            if ($price <= 0) {
                continue;
            }

            $this->db->insert('plan_prices', [
                'plan_id' => $planId,
                'billing_cycle' => $ezscaleCycle,
                'price' => $price,
            ]);

            $inserted++;
        }

        return $inserted;
    }

    private function parsePrice(string $price): float
    {
        $price = trim($price);

        if ($price === '' || $price === '-1.00' || $price === '-1') {
            return 0.0;
        }

        $parsed = (float) $price;

        return $parsed > 0 ? round($parsed, 2) : 0.0;
    }

    private function generateUniqueSlug(string $name): string
    {
        $baseSlug = strtolower(trim($name));
        $baseSlug = (string) preg_replace('/[^a-z0-9\s-]/', '', $baseSlug);
        $baseSlug = (string) preg_replace('/[\s-]+/', '-', $baseSlug);
        $baseSlug = trim($baseSlug, '-');

        if ($baseSlug === '') {
            $baseSlug = 'plan';
        }

        $slug = $baseSlug;
        $suffix = 2;

        while ($this->db->tableHasRow('plans', ['slug' => $slug])) {
            $slug = "{$baseSlug}-{$suffix}";
            $suffix++;
        }

        return $slug;
    }

    /**
     * Load plan mapping and skip list from plan_mapping.json.
     *
     * @return array{mappings: array<string, string>, skip: array<int, string>}
     */
    private function loadPlanMapping(): array
    {
        $path = $this->getPlanMappingPath();

        $contents = file_get_contents($path);

        if ($contents === false) {
            throw new RuntimeException("Cannot read plan_mapping.json at {$path}");
        }

        $decoded = json_decode($contents, true);

        if (! is_array($decoded) || ! array_key_exists('mappings', $decoded)) {
            throw new RuntimeException('plan_mapping.json must contain a "mappings" key');
        }

        $mappings = [];

        foreach ($decoded['mappings'] as $whmcsId => $slug) {
            $mappings[(string) $whmcsId] = (string) $slug;
        }

        $skip = [];

        if (array_key_exists('skip', $decoded) && is_array($decoded['skip'])) {
            $skip = array_map('strval', $decoded['skip']);
        }

        return ['mappings' => $mappings, 'skip' => $skip];
    }

    private function getPlanMappingPath(): string
    {
        return dirname(__DIR__, 2) . '/plan_mapping.json';
    }

    private function validatePlanMapping(): bool
    {
        $mappingPath = $this->getPlanMappingPath();

        if (! file_exists($mappingPath)) {
            $this->logger->error("Validation failed: plan_mapping.json not found at {$mappingPath}");

            return false;
        }

        $contents = file_get_contents($mappingPath);

        if ($contents === false) {
            $this->logger->error("Validation failed: cannot read plan_mapping.json");

            return false;
        }

        $decoded = json_decode($contents, true);

        if (! is_array($decoded) || ! array_key_exists('mappings', $decoded)) {
            $this->logger->error('Validation failed: plan_mapping.json must contain a "mappings" key');

            return false;
        }

        return true;
    }

    private function validateApi(): bool
    {
        try {
            $response = $this->api->call('GetProducts');

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

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

            return false;
        }

        return true;
    }
}
