<?php

declare(strict_types=1);

namespace ACP\Access;

use AC\Integration\IntegrationRepository;
use ACP\Access\Rule\ApiPermissionResponse;
use ACP\API;
use ACP\ApiFactory;
use ACP\LicenseKeyRepository;
use ACP\Type\Activation;
use ACP\Type\Activation\ExpiryDate;
use ACP\Type\Activation\Key;
use ACP\Type\Activation\RenewalMethod;
use ACP\Type\Activation\Status;
use ACP\Type\ActivationToken;
use ACP\Type\SiteUrl;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
use WP_Error;

class ActivationUpdater
{
    private ActivationKeyStorage $activation_key_storage;

    private ActivationStorage $activation_storage;

    private LicenseKeyRepository $license_key_repository;

    private ApiFactory $api_factory;

    private SiteUrl $site_url;

    private IntegrationRepository $integration_repository;

    private PermissionCheckerFactory $permission_factory;

    public function __construct(
        ActivationKeyStorage $activation_key_storage,
        ActivationStorage $activation_storage,
        LicenseKeyRepository $license_key_repository,
        ApiFactory $api_factory,
        SiteUrl $site_url,
        IntegrationRepository $integration_repository,
        PermissionCheckerFactory $permission_factory
    ) {
        $this->activation_key_storage = $activation_key_storage;
        $this->activation_storage = $activation_storage;
        $this->license_key_repository = $license_key_repository;
        $this->api_factory = $api_factory;
        $this->site_url = $site_url;
        $this->integration_repository = $integration_repository;
        $this->permission_factory = $permission_factory;
    }

    public function update(ActivationToken $token): API\Response
    {
        $api = $this->api_factory->create();

        $request = new API\Request\SubscriptionDetails(
            $this->site_url,
            $token,
            $this->integration_repository,
            $api->get_version()
        );

        $response = $api->dispatch($request);

        if ($response->has_error()) {
            // Remove license info when their subscription has not been found or the site is not registered.
            if (
                $this->has_error_code($response->get_error(), 'license_not_found') ||
                $this->has_error_code($response->get_error(), 'activation_not_registered')
            ) {
                $this->activation_key_storage->delete();
                $this->activation_storage->delete();
                $this->license_key_repository->delete();
                // clear permissions
                $this->permission_factory->create()->apply();
            }

            return $response;
        }

        // Merge API-returned permissions on top of the existing stored permissions.
        // populate() loads the stored state first so add_rule() overlays without clearing it.
        $this->permission_factory
            ->create()
            ->populate()
            ->add_rule(new ApiPermissionResponse($response))
            ->apply();

        $activation = $this->create_activation_from_response($response);
        $activation_key = $this->create_activation_key_from_response($response);

        if ($activation_key && $activation) {
            $this->activation_key_storage->save($activation_key);
            $this->activation_storage->save($activation_key, $activation);

            // old key is no longer needed since 5.7
            if ('subscription_key' === $token->get_type()) {
                $this->license_key_repository->delete();
            }
        }

        return $response;
    }

    private function create_activation_key_from_response(API\Response $api_response): ?Key
    {
        try {
            $key = new Key((string)$api_response->get('activation_key'));
        } catch (InvalidArgumentException $e) {
            return null;
        }

        return $key;
    }

    private function create_activation_from_response(API\Response $api_response): ?Activation
    {
        $expire_date = $api_response->get('expiry_date')
            ? DateTime::createFromFormat(
                'Y-m-d H:i:s',
                $api_response->get('expiry_date'),
                new DateTimeZone('Europe/Amsterdam')
            )
            : null;

        if ($expire_date === false) {
            return null;
        }

        if ($expire_date && $expire_date > DateTime::createFromFormat('Y-m-d', '2037-12-30')) {
            $expire_date = null;
        }

        $status = $api_response->get('status');

        if (! Status::is_valid($status)) {
            return null;
        }

        $method = $api_response->get('renewal_method');

        if (! RenewalMethod::is_valid($method)) {
            return null;
        }

        return new Activation(
            new Status($status),
            new RenewalMethod($method),
            $expire_date
                ? new ExpiryDate($expire_date)
                : null
        );
    }

    private function has_error_code(WP_Error $error, string $code): bool
    {
        return in_array($code, $error->get_error_codes(), true);
    }

}
