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

declare(strict_types=1);

use FeedManager\Integrations\Shopify\ShopifyAuthorizationCodeGrant;
use FeedManager\Support\DotenvLoader;
use FeedManager\Support\Env;
use FeedManager\Support\EnvFileEditor;

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

$argv = $_SERVER['argv'] ?? [];
$clientName = $argv[1] ?? null;
$action = $argv[2] ?? null;
$callbackUrl = $argv[3] ?? null;

$usage = <<<TXT
Usage:
  php bin/shopify-oauth <client-name> start
  php bin/shopify-oauth <client-name> exchange "<callback-url>"

TXT;

if (!is_string($clientName) || $clientName === '' || !is_string($action) || $action === '') {
    fwrite(STDERR, $usage);
    exit(1);
}

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

    $configPath = __DIR__ . '/../clients/' . $clientName . '/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 is missing shopify section.');
    }

    $shopDomain = Env::require((string) ($shopifyConfig['shop_domain_env'] ?? ''));
    $clientId = Env::require((string) ($shopifyConfig['client_id_env'] ?? ''));
    $clientSecret = Env::require((string) ($shopifyConfig['client_secret_env'] ?? ''));
    $redirectUri = Env::require((string) ($shopifyConfig['oauth_redirect_uri_env'] ?? ''));

    $scopesRaw = $shopifyConfig['oauth_scopes'] ?? [];
    if (!is_array($scopesRaw)) {
        throw new RuntimeException('Client config oauth_scopes must be an array.');
    }

    $stateFile = (string) ($shopifyConfig['oauth_state_file'] ?? (__DIR__ . '/../clients/' . $clientName . '/.oauth-state.json'));
    if ($stateFile === '') {
        throw new RuntimeException('OAuth state file path is empty.');
    }

    $grant = new ShopifyAuthorizationCodeGrant();

    if ($action === 'start') {
        $state = bin2hex(random_bytes(16));

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

        $statePayload = [
            'state' => $state,
            'shop' => $shopDomain,
            'created_at' => gmdate('c'),
        ];

        $written = file_put_contents($stateFile, json_encode($statePayload, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
        if ($written === false) {
            throw new RuntimeException(sprintf('Failed to write OAuth state file: %s', $stateFile));
        }

        $authorizeUrl = $grant->buildAuthorizeUrl(
            shopDomain: $shopDomain,
            clientId: $clientId,
            redirectUri: $redirectUri,
            scopes: $scopesRaw,
            state: $state
        );

        fwrite(STDOUT, "Open this URL in your browser and approve the app:\n\n");
        fwrite(STDOUT, $authorizeUrl . "\n\n");
        fwrite(STDOUT, "After approval, copy the full callback URL from your browser and run:\n");
        fwrite(STDOUT, sprintf("php bin/shopify-oauth %s exchange \"<callback-url>\"\n", $clientName));
        exit(0);
    }

    if ($action === 'exchange') {
        if (!is_string($callbackUrl) || trim($callbackUrl) === '') {
            throw new RuntimeException('Missing callback URL.');
        }

        if (!is_file($stateFile)) {
            throw new RuntimeException(sprintf('OAuth state file not found: %s. Run start first.', $stateFile));
        }

        $stateRaw = file_get_contents($stateFile);
        if (!is_string($stateRaw) || trim($stateRaw) === '') {
            throw new RuntimeException('OAuth state file is empty.');
        }

        /** @var array<string, mixed> $statePayload */
        $statePayload = json_decode($stateRaw, true, 512, JSON_THROW_ON_ERROR);
        $expectedState = (string) ($statePayload['state'] ?? '');
        if ($expectedState === '') {
            throw new RuntimeException('OAuth state file is missing state value.');
        }

        $verified = $grant->verifyCallbackUrl(
            callbackUrl: $callbackUrl,
            expectedShopDomain: $shopDomain,
            expectedState: $expectedState,
            clientSecret: $clientSecret
        );

        $tokenPayload = $grant->exchangeCodeForToken(
            shopDomain: $verified['shop'],
            clientId: $clientId,
            clientSecret: $clientSecret,
            code: $verified['code']
        );

        $envPath = __DIR__ . '/../.env';
        $accessTokenEnvKey = (string) ($shopifyConfig['access_token_env'] ?? 'SHOPIFY_ACCESS_TOKEN');
        EnvFileEditor::upsert($envPath, $accessTokenEnvKey, $tokenPayload['access_token']);

        @unlink($stateFile);

        fwrite(STDOUT, sprintf("Stored offline token in .env key %s\n", $accessTokenEnvKey));
        fwrite(STDOUT, sprintf("Granted scopes: %s\n", $tokenPayload['scope']));
        fwrite(STDOUT, "You can now run: php bin/export-feed " . $clientName . "\n");
        exit(0);
    }

    throw new RuntimeException(sprintf('Unknown action: %s', $action));
} catch (Throwable $e) {
    fwrite(STDERR, sprintf("Shopify OAuth failed: %s\n", $e->getMessage()));
    exit(1);
}
