<?php
namespace DevOwl\CookieConsentManagement\settings;

use DevOwl\CookieConsentManagement\consent\Consent;
use DevOwl\CookieConsentManagement\consent\Transaction;

/**
 * Abstract implementation of the settings for country bypass settings.
 */
abstract class AbstractCountryBypass extends BaseSettings {
    const CUSTOM_BYPASS = 'geolocation';
    const TYPE_ALL = 'all';
    const TYPE_ESSENTIALS = 'essentials';

    /**
     * A list of predefined lists for e.g. `GDPR` or `CCPA`.
     */
    const PREDEFINED_COUNTRY_LISTS = [
        // EU: https://reciprocitylabs.com/resources/what-countries-are-covered-by-gdpr/
        // EEA: https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Glossary:European_Economic_Area_(EEA)
        'GDPR' => [
            'AT',
            'BE',
            'BG',
            'HR',
            'CY',
            'CZ',
            'DK',
            'EE',
            'FI',
            'FR',
            'DE',
            'GR',
            'HU',
            'IE',
            'IS',
            'IT',
            'LI',
            'LV',
            'LT',
            'LU',
            'MT',
            'NL',
            'NO',
            'PL',
            'PT',
            'RO',
            'SK',
            'SI',
            'ES',
            'SE',
        ],
        'CCPA' => ['US'],
    ];

    /**
     * Get the country for the passed IP address.
     *
     * @param string $ipAddress
     * @return string
     */
    abstract public function lookupCountryCode($ipAddress);

    /**
     * Check if compatibility is enabled.
     *
     * @return boolean
     */
    abstract public function isActive();

    /**
     * Get the list of countries where the banner should be shown.
     *
     * @return string[]
     */
    abstract public function getCountriesRaw();

    /**
     * Get the type for the Country Bypass. Can be `all` or `essentials` (see class constants).
     *
     * @return string
     */
    abstract public function getType();

    /**
     * Get the list of countries where the banner should be shown, expanded with predefined lists (ISO 3166-1 alpha2).
     *
     * @return string[]
     */
    public function getCountries() {
        $result = [];

        // Expand predefined lists
        foreach ($this->getCountriesRaw() as $code) {
            if (strlen($code) !== 2) {
                $predefinedList = self::PREDEFINED_COUNTRY_LISTS[$code] ?? [];
                $result = array_merge($result, $predefinedList);
            } else {
                $result[] = $code;
            }
        }

        return $result;
    }

    /**
     * If Country Bypass is active and the requested IP address does match our settings, we can prepare a `Tansaction`
     * instance which we can instantly use to `Consent#commit` so the user does not see any cookie banner (see also
     * the term "Predecision Gateway").
     *
     * @param Consent $consent The current consent
     * @param Transaction $transaction A prepared transaction which has `userAgent`, `ipAddress` filled
     */
    public function probablyCreateTransaction($consent, $transaction) {
        $isLighthouse = preg_match('/chrome-lighthouse/i', $transaction->getUserAgent());

        if ($this->isActive() && !$isLighthouse) {
            // Lookup for the current country and do not show banner if it is outside our defined countries
            $countries = $this->getCountries();
            $countryCode = $this->lookupCountryCode($transaction->getIpAddress());

            if (!is_string($countryCode) || in_array(strtoupper($countryCode), $countries, true)) {
                // Skip custom bypass
                return false;
            }

            $transaction->setButtonClicked($this->getType() === self::TYPE_ALL ? 'implicit_all' : 'implicit_essential');
            $transaction->setCustomBypass(self::CUSTOM_BYPASS);

            // The GDPR does not apply here, so we do not need to set a TCF string
            $transaction->setTcfString(null);

            // Country bypassing does not need a GCM consent as this is configured through the `region` attribute in `gtag`
            $transaction->setGcmConsent(null);

            // Create decision for this bypass
            $type = $this->getType();
            $previousButtonClicked = $consent->getButtonClicked();

            /**
             * If the previous consent was also implicit (e.g. also through Geo-restriction), we continue with the
             * implicitness and do not opt-out any service.
             */
            $continueWithImplicitConsent =
                ($this->getType() === self::TYPE_ALL && $previousButtonClicked === 'implicit_all') ||
                ($this->getType() === self::TYPE_ESSENTIALS && $previousButtonClicked === 'implicit_essential');

            if (empty($consent->getUuid()) || $continueWithImplicitConsent) {
                // No previous consent, so create it from the given type
                $transaction->setDecision($consent->sanitizeDecision($type));
            } else {
                // There is a previous consent, modify it
                $transaction->setDecision(
                    $consent->optInOrOptOutExistingDecision(
                        $consent->getDecision(),
                        $type,
                        $type === 'all' ? 'optOut' : 'optIn'
                    )
                );
            }

            return true;
        }

        return false;
    }

    /**
     * Changes to the country database are published daily, but we do this only once a week.
     */
    public static function getNextUpdateTime() {
        return strtotime('next sunday 11:59 PM');
    }
}
