<?php

declare(strict_types=1);

require_once __DIR__ . '/TierPolicy.php';

final class MyronLicenseClient
{
    public function __construct(
        private readonly string $baseUrl,
        private readonly int $timeoutSeconds = 8,
    ) {
    }

    public static function fromEnvironment(): self
    {
        $baseUrl = trim((string) getenv('MYRON_LICENSE_API_URL'));
        if ($baseUrl === '') {
            $baseUrl = 'https://license.myroncms.com/api';
        }
        $timeout = (int) (getenv('MYRON_LICENSE_API_TIMEOUT') ?: 8);
        return new self($baseUrl, max(1, min(30, $timeout)));
    }

    /**
     * @param array<string, mixed> $request
     * @return array<string, mixed>
     */
    public function activate(array $request): array
    {
        return $this->post('/' . $this->apiVersionFor($request) . '/licenses/activate', $request);
    }

    /**
     * @param array<string, mixed> $request
     * @return array<string, mixed>
     */
    public function refresh(array $request): array
    {
        return $this->post('/' . $this->apiVersionFor($request) . '/licenses/refresh', $request);
    }

    /**
     * @param array<string, mixed> $request
     * @return array<string, mixed>
     */
    public function deactivate(array $request): array
    {
        return $this->post('/' . $this->apiVersionFor($request) . '/licenses/deactivate', $request);
    }

    public static function normalizeEnvironment(string $environment): string
    {
        $normalized = strtolower(trim($environment));
        return match ($normalized) {
            'local', 'localhost', 'dev-local' => 'localhost',
            'dev', 'development' => 'development',
            'stage', 'staging' => 'staging',
            'prod', 'production', '' => 'production',
            default => 'production',
        };
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function post(string $path, array $payload): array
    {
        $baseUrl = rtrim($this->baseUrl, '/');
        if ((string) getenv('MYRON_NO_OUTBOUND') === '1') {
            throw new RuntimeException('Outbound license service calls are disabled by MYRON_NO_OUTBOUND.');
        }

        if (str_starts_with($baseUrl, 'mock://')) {
            return $this->mockResponse($path, $payload);
        }

        $this->assertAllowedEndpoint($baseUrl);
        $json = json_encode($payload, JSON_UNESCAPED_SLASHES);
        if (!is_string($json)) {
            throw new RuntimeException('Unable to encode license request.');
        }

        $context = stream_context_create([
            'http' => [
                'method' => 'POST',
                'timeout' => $this->timeoutSeconds,
                'ignore_errors' => true,
                'header' => "Content-Type: application/json\r\nAccept: application/json\r\n",
                'content' => $json,
            ],
        ]);
        $response = @file_get_contents($baseUrl . $path, false, $context);
        if (!is_string($response)) {
            throw new RuntimeException('License service is unavailable.');
        }

        $decoded = json_decode($response, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('License service returned an invalid response.');
        }
        if (($decoded['ok'] ?? false) !== true) {
            $reason = trim((string) ($decoded['code'] ?? $decoded['reasonCode'] ?? 'license_validation_failed'));
            throw new RuntimeException($reason !== '' ? $reason : 'license_validation_failed');
        }

        return $decoded;
    }

    private function assertAllowedEndpoint(string $baseUrl): void
    {
        $scheme = strtolower((string) parse_url($baseUrl, PHP_URL_SCHEME));
        $host = strtolower((string) parse_url($baseUrl, PHP_URL_HOST));
        $isLocalhost = in_array($host, ['127.0.0.1', 'localhost', '::1'], true);
        if ($scheme !== 'https' && !($scheme === 'http' && $isLocalhost)) {
            throw new RuntimeException('License API endpoint must use HTTPS outside localhost development.');
        }
    }

    /**
     * @param array<string, mixed> $request
     */
    private function apiVersionFor(array $request): string
    {
        $capabilities = is_array($request['clientCapabilities'] ?? null) ? $request['clientCapabilities'] : [];
        $requested = strtolower(trim((string) ($capabilities['schema'] ?? getenv('MYRON_LICENSE_API_VERSION') ?: 'v1')));
        return $requested === 'v2' ? 'v2' : 'v1';
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function mockResponse(string $path, array $payload): array
    {
        $isV2 = str_starts_with($path, '/v2/');
        if ($path === '/v1/licenses/deactivate' || $path === '/v2/licenses/deactivate') {
            $activationId = trim((string) ($payload['activationId'] ?? ''));
            if ($activationId === '') {
                throw new RuntimeException('missing_activation_id');
            }
            if (str_contains(strtolower($activationId), 'missing') || str_contains(strtolower($activationId), 'fail')) {
                throw new RuntimeException('activation_not_found');
            }

            return [
                'ok' => true,
                'apiVersion' => $isV2 ? 'v2' : 'v1',
                'code' => 'deactivated',
                'activationId' => $activationId,
            ];
        }

        $licenseKey = trim((string) ($payload['licenseKey'] ?? ''));
        $deploymentKey = trim((string) ($payload['deploymentKey'] ?? ''));
        if ($licenseKey === '') {
            throw new RuntimeException('missing_license_key');
        }
        if (str_contains(strtolower($licenseKey), 'invalid')) {
            throw new RuntimeException('invalid_license_key');
        }

        $plan = 'core';

        $environment = self::normalizeEnvironment((string) ($payload['environment'] ?? 'production'));
        $deploymentGroup = $deploymentKey !== '' ? 'grp_' . substr(hash('sha256', $deploymentKey), 0, 12) : '';
        $licenseKeyLower = strtolower($licenseKey);
        $status = match (true) {
            str_contains($licenseKeyLower, 'expired') => MyronLicenseStatus::EXPIRED,
            str_contains($licenseKeyLower, 'payment_failed'), str_contains($licenseKeyLower, 'payment-failed') => MyronLicenseStatus::PAYMENT_FAILED,
            str_contains($licenseKeyLower, 'stale') => MyronLicenseStatus::STALE,
            str_contains($licenseKeyLower, 'disabled') => MyronLicenseStatus::DISABLED,
            default => MyronLicenseStatus::ACTIVE,
        };
        $now = gmdate('Y-m-d\\TH:i:s\\Z');
        $requestedSkus = [];
        if ($isV2 && is_array($payload['skus'] ?? null)) {
            foreach ($payload['skus'] as $requestedSku) {
                $skuId = trim((string) $requestedSku);
                if ($skuId !== '') {
                    $requestedSkus[$skuId] = true;
                }
            }
        }
        $includeFunnelsSku = $isV2
            && (isset($requestedSkus['pro-funnels']) || str_contains($licenseKeyLower, 'funnels'));
        $includeLmsSku = $isV2
            && (isset($requestedSkus['pro-lms']) || str_contains($licenseKeyLower, 'lms'));
        $includeCommerceSku = $isV2
            && (isset($requestedSkus['pro-commerce']) || str_contains($licenseKeyLower, 'commerce'));
        $includeDevopsSku = $isV2
            && (isset($requestedSkus['pro-devops']) || str_contains($licenseKeyLower, 'devops') || str_contains($licenseKeyLower, 'operations'));
        $includeByokAiSku = $isV2
            && (isset($requestedSkus['pro-byok-ai']) || str_contains($licenseKeyLower, 'byok') || str_contains($licenseKeyLower, 'llm'));
        $capabilities = [];
        if ($includeFunnelsSku) {
            foreach ([
                'modules.install',
                'analytics.providers',
                'attribution.remove_public_bar',
                'code.custom_injection',
                'commerce.checkout_primitives',
                'feeds.custom_api',
                'forms.webhooks',
                'site_type.mkt',
            ] as $capability) {
                $capabilities[$capability] = true;
            }
        }
        if ($includeLmsSku) {
            $capabilities['modules.install'] = true;
            $capabilities['site_type.edu'] = true;
        }
        if ($includeCommerceSku) {
            $capabilities['modules.install'] = true;
            $capabilities['payment.providers'] = true;
            $capabilities['commerce.full'] = true;
        }
        if ($includeDevopsSku) {
            $capabilities['modules.install'] = true;
            $capabilities['snapshots.devops'] = true;
            $capabilities['devops.environments'] = true;
            $capabilities['multi_environment_workflow'] = true;
            $capabilities['deployment_group'] = true;
        }
        if ($includeByokAiSku) {
            $capabilities['modules.install'] = true;
            $capabilities['ai.byok_provider_config'] = true;
            $capabilities['mcp.byok_ai_actions'] = true;
        }

        $snapshot = MyronEntitlementSnapshot::fromArray([
            'plan' => $plan,
            'status' => $status,
            'deploymentGroup' => $deploymentGroup,
            'environment' => $environment,
            'entitlements' => [
                'capabilities' => $capabilities,
            ],
            'validatedAt' => $now,
            'renewsAt' => gmdate('Y-m-d\\TH:i:s\\Z', time() + 31536000),
        ]);

        $response = [
            'ok' => true,
            'activationId' => 'act_' . substr(hash('sha256', $licenseKey . $deploymentKey . $path), 0, 16),
            'plan' => $snapshot->plan,
            'status' => $snapshot->status,
            'licenseFingerprint' => MyronLicenseRepository::fingerprint($licenseKey),
            'licenseMask' => MyronLicenseRepository::maskKey($licenseKey),
            'deploymentKeyMask' => $deploymentKey !== '' ? MyronLicenseRepository::maskKey($deploymentKey) : '',
            'deploymentGroup' => $snapshot->deploymentGroup,
            'environment' => $snapshot->environment,
            'entitlements' => [
                'capabilities' => $snapshot->capabilities,
                'limits' => $snapshot->limits,
            ],
            'expiresAt' => $snapshot->expiresAt,
            'renewsAt' => $snapshot->renewsAt,
            'graceEndsAt' => $snapshot->graceEndsAt,
            'validatedAt' => $snapshot->lastValidatedAt,
        ];
        if ($isV2) {
            $response['apiVersion'] = 'v2';
            $response['skuModel'] = 'module-items';
            $response['activeSkus'] = [];
            if ($includeFunnelsSku) {
                $response['activeSkus'][] = [
                    'id' => 'pro-funnels',
                    'displayName' => 'Funnels',
                    'targetType' => 'module-bundle',
                    'status' => $status === MyronLicenseStatus::ACTIVE ? 'active' : $status,
                    'renewedAt' => null,
                    'renewsAt' => $snapshot->renewsAt,
                ];
            }
            if ($includeLmsSku) {
                $response['activeSkus'][] = [
                    'id' => 'pro-lms',
                    'displayName' => 'LMS',
                    'targetType' => 'module-bundle',
                    'status' => $status === MyronLicenseStatus::ACTIVE ? 'active' : $status,
                    'renewedAt' => null,
                    'renewsAt' => $snapshot->renewsAt,
                ];
            }
            if ($includeCommerceSku) {
                $response['activeSkus'][] = [
                    'id' => 'pro-commerce',
                    'displayName' => 'Commerce',
                    'targetType' => 'module-bundle',
                    'status' => $status === MyronLicenseStatus::ACTIVE ? 'active' : $status,
                    'renewedAt' => null,
                    'renewsAt' => $snapshot->renewsAt,
                ];
            }
            if ($includeDevopsSku) {
                $response['activeSkus'][] = [
                    'id' => 'pro-devops',
                    'displayName' => 'Pro Operations',
                    'targetType' => 'module-bundle',
                    'status' => $status === MyronLicenseStatus::ACTIVE ? 'active' : $status,
                    'renewedAt' => null,
                    'renewsAt' => $snapshot->renewsAt,
                ];
            }
            if ($includeByokAiSku) {
                $response['activeSkus'][] = [
                    'id' => 'pro-byok-ai',
                    'displayName' => 'BYOK LLM',
                    'targetType' => 'module-bundle',
                    'status' => $status === MyronLicenseStatus::ACTIVE ? 'active' : $status,
                    'renewedAt' => null,
                    'renewsAt' => $snapshot->renewsAt,
                ];
            }
        }

        return $response;
    }
}
