#!/usr/bin/env php
<?php

declare(strict_types=1);

use FeedManager\Integrations\Shopify\ShopifyAccessTokenProvider;
use FeedManager\Integrations\Shopify\ShopifyGraphqlClient;
use FeedManager\Support\DotenvLoader;
use FeedManager\Support\Env;

require_once __DIR__ . '/../bootstrap.php';

const PRODUCTS_QUERY = <<<'GRAPHQL'
query ProductsMetafields($first: Int!, $after: String) {
  products(first: $first, after: $after, sortKey: ID) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      id
      legacyResourceId
      title
      metafields(first: 100) {
        nodes {
          namespace
          key
          type
          value
        }
      }
    }
  }
}
GRAPHQL;

const VARIANTS_QUERY = <<<'GRAPHQL'
query VariantMetafields($first: Int!, $after: String) {
  productVariants(first: $first, after: $after) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      id
      legacyResourceId
      title
      product {
        legacyResourceId
        title
      }
      metafields(first: 100) {
        nodes {
          namespace
          key
          type
          value
        }
      }
    }
  }
}
GRAPHQL;

$client = $argv[1] ?? null;
$pageSize = 10;
$outputPath = null;

foreach (array_slice($argv, 2) as $arg) {
    if (str_starts_with((string) $arg, '--page-size=')) {
        $pageSize = (int) substr((string) $arg, strlen('--page-size='));
    }
    if (str_starts_with((string) $arg, '--output=')) {
        $outputPath = (string) substr((string) $arg, strlen('--output='));
    }
}

if (!is_string($client) || trim($client) === '') {
    fwrite(STDERR, "Usage: php bin/list-metafields <client> [--page-size=10] [--output=/abs/path/report.json]\n");
    exit(1);
}

if ($pageSize < 1) {
    $pageSize = 1;
}
if ($pageSize > 100) {
    $pageSize = 100;
}

try {
    DotenvLoader::load(__DIR__ . '/../.env');

    $configPath = __DIR__ . '/../clients/' . $client . '/config.php';
    if (!is_file($configPath)) {
        throw new RuntimeException(sprintf('Client config not found: %s', $configPath));
    }

    /** @var array<string, mixed> $config */
    $config = require $configPath;
    $shopifyConfig = $config['shopify'] ?? null;
    if (!is_array($shopifyConfig)) {
        throw new RuntimeException('Client config missing shopify section');
    }

    $shopDomain = Env::require((string) ($shopifyConfig['shop_domain_env'] ?? ''));
    $directAccessToken = Env::get((string) ($shopifyConfig['access_token_env'] ?? ''));
    $clientId = Env::get((string) ($shopifyConfig['client_id_env'] ?? ''));
    $clientSecret = Env::get((string) ($shopifyConfig['client_secret_env'] ?? ''));
    $apiVersion = Env::get((string) ($shopifyConfig['api_version_env'] ?? 'SHOPIFY_API_VERSION'), (string) ($shopifyConfig['api_version_default'] ?? '2025-10'));
    $requestTimeoutSeconds = (int) Env::get((string) ($shopifyConfig['request_timeout_env'] ?? 'SHOPIFY_REQUEST_TIMEOUT_SECONDS'), '120');

    if ($apiVersion === null || $apiVersion === '') {
        throw new RuntimeException('Shopify API version missing');
    }
    if ($requestTimeoutSeconds < 10) {
        $requestTimeoutSeconds = 10;
    }

    $tokenProvider = new ShopifyAccessTokenProvider(
        shopDomain: $shopDomain,
        directAccessToken: $directAccessToken,
        clientId: $clientId,
        clientSecret: $clientSecret,
    );
    $accessToken = $tokenProvider->resolve();

    $shopifyClient = new ShopifyGraphqlClient(
        shopDomain: $shopDomain,
        accessToken: $accessToken,
        apiVersion: $apiVersion,
        timeoutSeconds: $requestTimeoutSeconds,
    );

    [$productAgg, $productCount] = collectProductMetafields($shopifyClient, $pageSize);
    [$variantAgg, $variantCount] = collectVariantMetafields($shopifyClient, $pageSize);

    $report = [
        'generated_at' => gmdate('c'),
        'client' => $client,
        'shop_domain' => $shopDomain,
        'api_version' => $apiVersion,
        'page_size' => $pageSize,
        'totals' => [
            'products_scanned' => $productCount,
            'variants_scanned' => $variantCount,
            'product_metafield_keys' => count($productAgg),
            'variant_metafield_keys' => count($variantAgg),
        ],
        'product_metafields' => normalizeMetafieldAggregate($productAgg),
        'variant_metafields' => normalizeMetafieldAggregate($variantAgg),
    ];

    if (!is_string($outputPath) || trim($outputPath) === '') {
        $outputPath = __DIR__ . '/../clients/' . $client . '/output/shopify-metafields-report.json';
    }

    $directory = dirname($outputPath);
    if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
        throw new RuntimeException(sprintf('Failed to create output directory: %s', $directory));
    }

    $encoded = json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    if ($encoded === false) {
        throw new RuntimeException('Failed to encode JSON report');
    }

    if (file_put_contents($outputPath, $encoded . PHP_EOL) === false) {
        throw new RuntimeException(sprintf('Failed to write report: %s', $outputPath));
    }

    fwrite(STDOUT, sprintf("Scanned products=%d variants=%d\n", $productCount, $variantCount));
    fwrite(STDOUT, sprintf("Metafield keys: product=%d variant=%d\n", count($productAgg), count($variantAgg)));
    fwrite(STDOUT, sprintf("Report: %s\n", $outputPath));

    $previewCount = 12;
    $productPreview = array_slice(array_keys($productAgg), 0, $previewCount);
    $variantPreview = array_slice(array_keys($variantAgg), 0, $previewCount);
    fwrite(STDOUT, "\nProduct metafield key preview:\n");
    foreach ($productPreview as $key) {
        fwrite(STDOUT, sprintf("- %s\n", $key));
    }
    fwrite(STDOUT, "\nVariant metafield key preview:\n");
    foreach ($variantPreview as $key) {
        fwrite(STDOUT, sprintf("- %s\n", $key));
    }

    exit(0);
} catch (Throwable $e) {
    fwrite(STDERR, sprintf("Metafield scan failed: %s\n", $e->getMessage()));
    exit(1);
}

/**
 * @return array{0: array<string, array<string, mixed>>, 1: int}
 */
function collectProductMetafields(ShopifyGraphqlClient $client, int $pageSize): array
{
    $after = null;
    $aggregate = [];
    $productCount = 0;

    do {
        $response = $client->query(PRODUCTS_QUERY, [
            'first' => $pageSize,
            'after' => $after,
        ]);

        $connection = $response['data']['products'] ?? null;
        if (!is_array($connection)) {
            throw new RuntimeException('Missing products connection in Shopify response.');
        }

        $nodes = $connection['nodes'] ?? [];
        if (!is_array($nodes)) {
            throw new RuntimeException('Invalid products nodes payload.');
        }

        foreach ($nodes as $product) {
            if (!is_array($product)) {
                continue;
            }
            $productCount++;
            aggregateMetafields($aggregate, $product['metafields']['nodes'] ?? []);
        }

        $pageInfo = $connection['pageInfo'] ?? null;
        if (!is_array($pageInfo)) {
            throw new RuntimeException('Missing products pageInfo in Shopify response.');
        }

        $hasNextPage = (bool) ($pageInfo['hasNextPage'] ?? false);
        $after = $hasNextPage ? (string) ($pageInfo['endCursor'] ?? '') : null;
        if ($hasNextPage && $after === '') {
            throw new RuntimeException('Products pagination indicated next page but no endCursor was returned.');
        }
    } while ($after !== null);

    ksort($aggregate);

    return [$aggregate, $productCount];
}

/**
 * @return array{0: array<string, array<string, mixed>>, 1: int}
 */
function collectVariantMetafields(ShopifyGraphqlClient $client, int $pageSize): array
{
    $after = null;
    $aggregate = [];
    $variantCount = 0;

    do {
        $response = $client->query(VARIANTS_QUERY, [
            'first' => $pageSize,
            'after' => $after,
        ]);

        $connection = $response['data']['productVariants'] ?? null;
        if (!is_array($connection)) {
            throw new RuntimeException('Missing productVariants connection in Shopify response.');
        }

        $nodes = $connection['nodes'] ?? [];
        if (!is_array($nodes)) {
            throw new RuntimeException('Invalid productVariants nodes payload.');
        }

        foreach ($nodes as $variant) {
            if (!is_array($variant)) {
                continue;
            }
            $variantCount++;
            aggregateMetafields($aggregate, $variant['metafields']['nodes'] ?? []);
        }

        $pageInfo = $connection['pageInfo'] ?? null;
        if (!is_array($pageInfo)) {
            throw new RuntimeException('Missing productVariants pageInfo in Shopify response.');
        }

        $hasNextPage = (bool) ($pageInfo['hasNextPage'] ?? false);
        $after = $hasNextPage ? (string) ($pageInfo['endCursor'] ?? '') : null;
        if ($hasNextPage && $after === '') {
            throw new RuntimeException('ProductVariants pagination indicated next page but no endCursor was returned.');
        }
    } while ($after !== null);

    ksort($aggregate);

    return [$aggregate, $variantCount];
}

/**
 * @param array<string, array<string, mixed>> $aggregate
 * @param mixed $metafieldNodes
 */
function aggregateMetafields(array &$aggregate, mixed $metafieldNodes): void
{
    if (!is_array($metafieldNodes)) {
        return;
    }

    foreach ($metafieldNodes as $metafield) {
        if (!is_array($metafield)) {
            continue;
        }

        $namespace = trim((string) ($metafield['namespace'] ?? ''));
        $key = trim((string) ($metafield['key'] ?? ''));
        if ($namespace === '' || $key === '') {
            continue;
        }

        $fullKey = sprintf('%s.%s', $namespace, $key);
        if (!isset($aggregate[$fullKey])) {
            $aggregate[$fullKey] = [
                'namespace' => $namespace,
                'key' => $key,
                'types' => [],
                'occurrences' => 0,
                'non_empty_values' => 0,
                'sample_values' => [],
            ];
        }

        $type = trim((string) ($metafield['type'] ?? ''));
        if ($type !== '') {
            $aggregate[$fullKey]['types'][$type] = true;
        }

        $aggregate[$fullKey]['occurrences'] = (int) $aggregate[$fullKey]['occurrences'] + 1;

        $value = trim((string) ($metafield['value'] ?? ''));
        if ($value !== '') {
            $aggregate[$fullKey]['non_empty_values'] = (int) $aggregate[$fullKey]['non_empty_values'] + 1;

            $sample = $value;
            if (mb_strlen($sample) > 120) {
                $sample = mb_substr($sample, 0, 117) . '...';
            }

            if (count($aggregate[$fullKey]['sample_values']) < 3 && !in_array($sample, $aggregate[$fullKey]['sample_values'], true)) {
                $aggregate[$fullKey]['sample_values'][] = $sample;
            }
        }
    }
}

/**
 * @param array<string, array<string, mixed>> $aggregate
 * @return array<int, array<string, mixed>>
 */
function normalizeMetafieldAggregate(array $aggregate): array
{
    $rows = [];

    foreach ($aggregate as $row) {
        $types = array_keys((array) ($row['types'] ?? []));
        sort($types);

        $rows[] = [
            'namespace' => (string) ($row['namespace'] ?? ''),
            'key' => (string) ($row['key'] ?? ''),
            'types' => $types,
            'occurrences' => (int) ($row['occurrences'] ?? 0),
            'non_empty_values' => (int) ($row['non_empty_values'] ?? 0),
            'sample_values' => array_values((array) ($row['sample_values'] ?? [])),
        ];
    }

    usort(
        $rows,
        static fn (array $a, array $b): int => strcmp(
            sprintf('%s.%s', (string) $a['namespace'], (string) $a['key']),
            sprintf('%s.%s', (string) $b['namespace'], (string) $b['key'])
        )
    );

    return $rows;
}
