<?php

declare(strict_types=1);

require_once __DIR__ . '/DowngradeCompatibilityReport.php';
require_once __DIR__ . '/LicenseRemediationState.php';
require_once __DIR__ . '/TierPolicy.php';

final class MyronLicenseActivationService
{
    public const REMOVE_CONFIRMATION_PHRASE = 'remove license';

    public function __construct(
        private readonly MyronLicenseClient $client,
        private readonly ?MyronLicenseRepository $repository = null,
        private readonly ?MyronLicenseAuditRepository $auditRepository = null,
    ) {
    }

    /**
     * @param array<string, mixed> $input
     * @return array{payload:array<string,mixed>, licenseKey:string, deploymentKey:string}
     */
    public function activateFromInput(array $input, string $siteUrl): array
    {
        $licenseKey = trim((string) ($input['license_key'] ?? $input['licenseKey'] ?? ''));
        $deploymentKey = trim((string) ($input['deployment_key'] ?? $input['deploymentKey'] ?? ''));
        $editionHint = MyronEntitlementSnapshot::normalizePlan((string) ($input['license_plan'] ?? $input['editionHint'] ?? 'core'));
        $environment = MyronLicenseClient::normalizeEnvironment((string) ($input['license_environment'] ?? $input['environment'] ?? 'production'));
        $apiVersion = strtolower(trim((string) ($input['api_version'] ?? $input['apiVersion'] ?? '')));

        if ($licenseKey === '') {
            throw new InvalidArgumentException('License key is required.');
        }
        if (MyronTierPolicy::requiresDeploymentKey($editionHint) && $deploymentKey === '') {
            throw new InvalidArgumentException('Deployment key is required for Pro and Agency licenses.');
        }

        $request = [
            'licenseKey' => $licenseKey,
            'deploymentKey' => $deploymentKey,
            'editionHint' => $editionHint,
            'siteUrl' => $siteUrl,
            'environment' => $environment,
            'instanceFingerprint' => $this->instanceFingerprint($siteUrl),
        ];
        if ($apiVersion === 'v2') {
            $request['skus'] = is_array($input['skus'] ?? null) ? $input['skus'] : [];
            $request['clientCapabilities'] = ['schema' => 'v2', 'supportsCoexistence' => true];
        }

        $payload = $this->client->activate($request);

        if ($this->repository !== null) {
            $currentSnapshot = $this->repository->currentSnapshot();
            $downgradeReport = MyronDowngradeCompatibilityReport::build($currentSnapshot, (string) ($payload['plan'] ?? $editionHint));
            if ($downgradeReport->requiresBackupAcknowledgement() && empty($input['confirm_downgrade_backup'])) {
                $this->auditRepository?->append('downgrade.compatibility', 'blocked', 'downgrade_backup_required', [
                    'plan' => $currentSnapshot->plan,
                    'status' => $currentSnapshot->status,
                    'targetPlan' => (string) ($payload['plan'] ?? $editionHint),
                    'compatibilityReport' => $downgradeReport->toArray(),
                    'dataPreservation' => 'Customer data remains preserved and exportable; acknowledge backup/export before package removal remediation.',
                ]);
                throw new RuntimeException('downgrade_backup_required');
            }
            if ($downgradeReport->isDowngrade()) {
                $this->auditRepository?->append('downgrade.compatibility', 'ok', 'backup_acknowledged', [
                    'plan' => $currentSnapshot->plan,
                    'status' => $currentSnapshot->status,
                    'targetPlan' => (string) ($payload['plan'] ?? $editionHint),
                    'compatibilityReport' => $downgradeReport->toArray(),
                ]);
            }
            $snapshot = $this->repository->saveActivationPayload($payload, $licenseKey, $deploymentKey);
            $this->auditRepository?->append('activate', 'ok', '', $snapshot->toArray());
        }

        return [
            'payload' => $payload,
            'licenseKey' => $licenseKey,
            'deploymentKey' => $deploymentKey,
        ];
    }

    /**
     * @param array<string, mixed> $input
     */
    public function refreshFromInput(array $input, string $siteUrl, string $actorUserId = ''): MyronEntitlementSnapshot
    {
        if ($this->repository === null) {
            throw new RuntimeException('License repository is required for refresh.');
        }

        try {
            $result = $this->activateFromInput($input, $siteUrl);
            $snapshot = $this->repository->currentSnapshot();
            $this->auditRepository?->append('refresh', 'ok', '', $snapshot->toArray(), $actorUserId);
            return $snapshot;
        } catch (Throwable $exception) {
            $reason = $this->publicReasonCode($exception);
            $this->repository->recordValidationFailure($reason, true);
            $remediationState = $this->repository->currentRemediationState();
            $this->auditRepository?->append('refresh', 'failed', $reason, [
                'status' => 'failed',
                'reasonCode' => $reason,
                'remediationState' => $remediationState->toInternalAuditDetail(),
            ], $actorUserId);
            $this->auditRepository?->appendRemediationReview($reason, [
                'status' => 'failed',
                'reasonCode' => $reason,
                'remediationState' => $remediationState->toInternalAuditDetail(),
            ], $actorUserId);
            throw new RuntimeException($this->safeErrorMessage($reason));
        }
    }

    public function persistActivationResult(array $activationResult): MyronEntitlementSnapshot
    {
        if ($this->repository === null) {
            throw new RuntimeException('License repository is required to persist activation.');
        }

        $payload = is_array($activationResult['payload'] ?? null) ? $activationResult['payload'] : [];
        $licenseKey = (string) ($activationResult['licenseKey'] ?? '');
        $deploymentKey = (string) ($activationResult['deploymentKey'] ?? '');
        $snapshot = $this->repository->saveActivationPayload($payload, $licenseKey, $deploymentKey);
        $this->auditRepository?->append('activate', 'ok', '', $snapshot->toArray());
        return $snapshot;
    }

    public function removeCurrentLicense(string $siteUrl, string $actorUserId, string $confirmationPhrase): void
    {
        $this->assertRemoveConfirmed($confirmationPhrase);
        if ($this->repository === null) {
            throw new RuntimeException('License repository is required for removal.');
        }

        $row = $this->repository->current();
        if ($row === null) {
            throw new RuntimeException('No license is activated on this site.');
        }

        $activationId = trim((string) ($row['activation_id'] ?? ''));
        if ($activationId === '') {
            throw new RuntimeException('The current license activation is missing its remote activation id.');
        }

        $snapshot = $this->repository->currentSnapshot();
        $detail = $snapshot->toArray();
        $detail['activationId'] = $activationId;

        try {
            $this->client->deactivate([
                'activationId' => $activationId,
                'siteUrl' => $siteUrl,
                'environment' => $snapshot->environment,
                'licenseFingerprint' => (string) ($row['license_fingerprint'] ?? ''),
                'instanceFingerprint' => $this->instanceFingerprint($siteUrl),
            ]);
            $this->repository->clearActivationState();
            $this->auditRepository?->append('remove', 'ok', 'remote_deactivated', $detail, $actorUserId);
        } catch (Throwable $exception) {
            $reason = $this->publicReasonCode($exception);
            $detail['status'] = 'failed';
            $detail['reasonCode'] = $reason;
            $this->auditRepository?->append('remove', 'failed', $reason, $detail, $actorUserId);
            throw new RuntimeException('License removal could not notify the license service. Try again before removing this license.');
        }
    }

    public function assertRemoveConfirmed(string $confirmationPhrase): void
    {
        if (trim($confirmationPhrase) !== self::REMOVE_CONFIRMATION_PHRASE) {
            throw new InvalidArgumentException('Type "' . self::REMOVE_CONFIRMATION_PHRASE . '" to remove this license.');
        }
    }

    public function publicReasonCode(Throwable $exception): string
    {
        $message = strtolower(trim($exception->getMessage()));
        $message = preg_replace('/[^a-z0-9_\\-]/', '_', $message) ?? 'license_validation_failed';
        return substr($message !== '' ? $message : 'license_validation_failed', 0, 120);
    }

    public function safeErrorMessage(string $reasonCode): string
    {
        return match ($reasonCode) {
            'missing_license_key' => 'License key is required.',
            'deployment_key_required' => 'Deployment key is required for this license.',
            'invalid_license_key' => 'License validation failed.',
            'expired' => 'This license is expired.',
            'disabled' => 'This license is disabled.',
            'payment_failed' => 'This license requires billing attention.',
            'downgrade_backup_required' => 'Review the downgrade compatibility report and confirm that site data has been exported or backed up before changing to a lower tier.',
            'service_unavailable', 'license_service_is_unavailable' => 'The license service is unavailable. Try again shortly.',
            default => 'License validation failed.',
        };
    }

    private function instanceFingerprint(string $siteUrl): string
    {
        $host = strtolower((string) parse_url($siteUrl, PHP_URL_HOST));
        return hash('sha256', $host !== '' ? $host : $siteUrl);
    }
}
