<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

namespace Piwik\Plugins\PrivacyManager\tests\System;

use Piwik\Access;
use Piwik\Common;
use Piwik\Config;
use Piwik\Db;
use Piwik\Plugins\PrivacyManager\API;
use Piwik\Plugins\PrivacyManager\FeatureFlags\PrivacyCompliance;
use Piwik\Plugins\PrivacyManager\tests\Fixtures\MultipleSitesMultipleVisitsFixture;
use Piwik\Policy\CnilPolicy;
use Piwik\Tests\Framework\TestCase\SystemTestCase;

/**
 * @group PrivacyManager
 * @group APITest
 * @group Plugins
 */
class APITest extends SystemTestCase
{
    /**
     * @var MultipleSitesMultipleVisitsFixture
     */
    public static $fixture = null; // initialized below class definition

    /**
     * @var API
     */
    private $api;

    private $testEnvironment;

    public function setUp(): void
    {
        parent::setUp();
        $this->api = API::getInstance();
        $this->testEnvironment = static::$fixture->getTestEnvironment();
    }

    public function tearDown(): void
    {
        parent::tearDown();
        $this->setComplianceFeatureFlag(false);
        $policyConfigSections = [
            'CnilPolicy',
        ];
        foreach ($policyConfigSections as $section) {
            Config::getInstance()->{$section} = null;
        }
    }

    public function testExportDataSubjectsFailsWhenNoVisitsGiven()
    {
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('No list of visits given');

        $this->assertNull($this->api->exportDataSubjects(false));
    }

    public function testExportDataSubjectsWhenOneVisitGiven()
    {
        $result = $this->api->exportDataSubjects([['idsite' => '1', 'idvisit' => '1']]);
        $this->assertJsonResponse('exportDataSubject_oneVisitGiven', $result);
    }

    public function testExportDataSubjectsWhenNotMatchingVisitGiven()
    {
        $noMatch = $this->api->exportDataSubjects([['idsite' => '9999', 'idvisit' => '9999']]);
        $this->assertJsonResponse('exportDataSubject_noMatch', $noMatch);
    }

    public function testExportDataSubjectsWhenAllVisitsGiven()
    {
        $rows = Db::fetchAll('SELECT idsite, idvisit from ' . Common::prefixTable('log_visit'));
        $result = $this->api->exportDataSubjects($rows);
        $this->assertJsonResponse('exportDataSubject_allVisits', $result);
    }

    public function testExportDataSubjectsFailsWhenMissingIdSite()
    {
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('No idsite key set for visit at index 1');

        $this->assertNull($this->api->exportDataSubjects([['idsite' => '9999', 'idvisit' => '9999'], []]));
    }

    private function assertJsonResponse($fileName, $result)
    {
        $result = MultipleSitesMultipleVisitsFixture::cleanResult($result);
        $result = json_encode($result, JSON_PRETTY_PRINT);
        $fileExpected = PIWIK_DOCUMENT_ROOT . '/plugins/PrivacyManager/tests/System/expected/' . $fileName . '.json';
        $fileProcessed = str_replace('/expected/', '/processed/', $fileExpected);
        \Piwik\Filesystem::mkdir(dirname($fileProcessed));
        file_put_contents($fileProcessed, $result);

        $this->assertJsonStringEqualsJsonFile($fileExpected, $result);
    }

    /**
     * @dataProvider getApiForTesting
     */
    public function testApi($api, $params)
    {
        $params['xmlFieldsToRemove'] = ['totalEcommerceRevenue', 'revenue', 'revenueDiscount'];
        $this->runApiTests($api, $params);
    }

    public function getApiForTesting()
    {
        $api = [
            'PrivacyManager.getAvailableVisitColumnsToAnonymize',
            'PrivacyManager.getAvailableLinkVisitActionColumnsToAnonymize',
        ];

        $apiToTest   = [];
        $apiToTest[] = [$api,
            [
                'idSite'     => 1,
                'date'       => self::$fixture->dateTime,
                'periods'    => ['day'],
                'testSuffix' => '',
            ],
        ];

        $apiToTest[] = [['Live.getLastVisitsDetails'],
            [
                'idSite'     => 'all',
                'date'       => self::$fixture->dateTime,
                'periods'    => ['year'],
                'otherRequestParameters' => ['filter_limit' => '-1'],
                'testSuffix' => 'allSites',
            ],
        ];

        $apiToTest[] = [['Live.getLastVisitsDetails'],
            [
                'idSite'     => 'all',
                'date'       => self::$fixture->dateTime,
                'periods'    => ['year'],
                'otherRequestParameters' => ['doNotFetchActions' => '1', 'filter_limit' => '-1'],
                'testSuffix' => 'allSites_noActions',
            ],
        ];

        return $apiToTest;
    }

    public function testFindDataSubjectsAllSites()
    {
        $this->runAnyApiTest('PrivacyManager.findDataSubjects', 'allSites', [
            'idSite'     => 'all',
            'segment'    => 'countryCode==CN',
        ]);
    }

    public function testFindDataSubjectsSpecificSite()
    {
        $this->runAnyApiTest('PrivacyManager.findDataSubjects', 'specificSite', [
            'idSite'     => '5',
            'segment'    => 'countryCode==CN',
        ]);
    }

    public function testFindDataSubjectsSpecificSiteNoVisitorLogs()
    {
        $this->runAnyApiTest('PrivacyManager.findDataSubjects', 'specificSiteNoVisitorLogs', [
            'idSite'     => '3',
            'segment'    => 'countryCode==CN',
        ]);
    }

    private function setComplianceFeatureFlag(bool $enableFlag): void
    {
        $config = Config::getInstance();
        $featureFlag = new PrivacyCompliance();
        $featureFlagConfig = $featureFlag->getName() . '_feature';

        if ($enableFlag) {
            $config->FeatureFlags = [$featureFlagConfig => 'enabled'];
        } else {
            $config->FeatureFlags = [$featureFlagConfig => 'disabled'];
        }
    }

    public function testGetComplianceStatusReturnsErrorIfFeatureFlagDisabled(): void
    {
        $this->setComplianceFeatureFlag(false);

        $this->runApiTests('PrivacyManager.getComplianceStatus', [
            'testSuffix' => 'featureFlagDisabled',
            'otherRequestParameters' => [
                'idSite' => '1',
                'complianceType' => 'cnil_v1',
            ],
        ]);
    }

    public function testGetComplianceStatusReturnsErrorIfComplianceTypeIsNotCnil(): void
    {
        $this->setComplianceFeatureFlag(true);

        $this->runApiTests('PrivacyManager.getComplianceStatus', [
            'testSuffix' => 'complianceTypeNotCnil',
            'otherRequestParameters' => [
                'idSite' => '1',
                'complianceType' => 'something else not valid',
            ],
        ]);
    }

    public function testGetComplianceStatusReturnsErrorIfNotSuperAdmin(): void
    {
        $access = Access::getInstance();
        $originalAccess = $access->hasSuperUserAccess();

        try {
            $access->setSuperUserAccess(false);

            $this->setComplianceFeatureFlag(true);

            $this->runApiTests('PrivacyManager.getComplianceStatus', [
                'testSuffix' => 'notSuperAdmin',
                'otherRequestParameters' => [
                    'idSite' => '1',
                    'complianceType' => 'cnil_v1',
                ],
            ]);
        } finally {
            $access->setSuperUserAccess($originalAccess);
        }
    }

    public function testGetComplianceStatusReturnsComplianceStatus(): void
    {
        $this->setComplianceFeatureFlag(true);

        $this->runApiTests('PrivacyManager.getComplianceStatus', [
            'otherRequestParameters' => [
                'idSite' => '1',
                'complianceType' => 'cnil_v1',
            ],
        ]);
    }

    public function getCompliancePolicyConfigValues()
    {
        yield ['CnilPolicy', 'cnil_v1', 'cnil_v1_policy_enabled', 0, 'configControlledDisabled'];
        yield ['CnilPolicy', 'cnil_v1', 'cnil_v1_policy_enabled', 1, 'configControlledEnabled'];
    }

    public function testGetAnonymisationSettingsDoesNotReturnsExtraMetadataForSystemSettingsWhenFeatureFlagEnabled(): void
    {
        $this->setComplianceFeatureFlag(true);

        // fixture disables all anonymisation, so we expect ipAddressMaskLength = 0 and ipAnonymizerEnabled = 0 in the response
        $this->runApiTests('PrivacyManager.getAnonymisationSettings', [
            'testSuffix' => '_compliancePolicyFeatureFlagEnabled',
        ]);
    }

    public function testGetAnonymisationSettingsReturnsExtraMetadataForSystemSettingsWhenPolicyEnforced(): void
    {
        $this->setComplianceFeatureFlag(true);
        CnilPolicy::setActiveStatus(null, true);

        $this->runApiTests('PrivacyManager.getAnonymisationSettings', [
            'testSuffix' => '_compliancePolicyEnforcedSystem',
        ]);

        CnilPolicy::setActiveStatus(null, false);
        $this->setComplianceFeatureFlag(false);
    }

    public function testGetAnonymisationSettingsReturnsExtraMetadataForWebsiteSettingsWhenPolicyEnforced(): void
    {
        $this->setComplianceFeatureFlag(true);
        CnilPolicy::setActiveStatus(1, true);

        $this->runApiTests('PrivacyManager.getAnonymisationSettings', [
            'testSuffix' => '_compliancePolicyEnforcedWebsite',
            'otherRequestParameters' => [
                'idSiteSpecific' => '1',
            ],
        ]);

        CnilPolicy::setActiveStatus(1, false);
        $this->setComplianceFeatureFlag(false);
    }

    /**
     * @dataProvider getCompliancePolicyConfigValues
     */
    public function testGetComplianceStatusConfigControlled(
        string $configSection,
        string $policyIdentifier,
        string $configKey,
        int $configValToSet,
        string $testSuffix
    ): void {
        $this->setComplianceFeatureFlag(true);
        Config::getInstance()->{$configSection}[$configKey] = $configValToSet;

        $this->runApiTests('PrivacyManager.getComplianceStatus', [
            'testSuffix' => $testSuffix,
            'otherRequestParameters' => [
                'idSite' => '1',
                'complianceType' => $policyIdentifier,
            ],
        ]);
        Config::getInstance()->{$configSection} = null;
    }

    public static function getOutputPrefix()
    {
        return '';
    }

    public static function getPathToTestDirectory()
    {
        return dirname(__FILE__);
    }
}

APITest::$fixture = new MultipleSitesMultipleVisitsFixture();
