<?php

namespace WPStaging\Pro\Backup\Storage\Storages\Amazon;

use WPStaging\Pro\Backup\Storage\AbstractStorage;
use WPStaging\Vendor\Aws\S3\S3Client;
use Exception;
use WPStaging\Framework\Utils\Sanitize;

use function WPStaging\functions\debug_log;

class S3 extends AbstractStorage
{
    const S3_VERSION = '2006-03-01';

    /** @var Sanitize */
    protected $sanitize;

    public function __construct(Sanitize $sanitize)
    {
        $this->identifier = 'amazons3';
        $this->label = 'Amazon S3';
        $this->sanitize = $sanitize;
    }

    public function authenticate()
    {
        // no-op
    }

    /**
     * @return bool
     */
    public function testConnection()
    {
        try {
            $accessKey = isset($_POST['access_key']) ? $this->sanitize->sanitizePassword($_POST['access_key']) : '';
            $secretKey = isset($_POST['secret_key']) ? $this->sanitize->sanitizePassword($_POST['secret_key']) : '';
            $region = isset($_POST['region']) ? $this->sanitize->sanitizeString($_POST['region']) : '';

            // Instantiate the S3 client with your AWS credentials
            $s3Client = new S3Client($this->getConfigOptions($accessKey, $secretKey, $region));

            $buckets = $s3Client->listBuckets();
        } catch (Exception $ex) {
            debug_log("S3 Client : " . $ex->getMessage());
            return false;
        }

        return true;
    }

    /** @return S3Client|false */
    public function getClient($options = null)
    {
        if ($options === null) {
            $options = $this->getOptions();
        }

        try {
            // Instantiate the S3 client with your AWS credentials
            $s3Client = new S3Client($this->getConfigOptions($options['accessKey'], $options['secretKey'], $options['region']));
        } catch (Exception $ex) {
            debug_log($ex->getMessage());
            return false;
        }

        return $s3Client;
    }

    /**
     * @param array $settings
     * @return bool
     */
    public function updateSettings($settings)
    {
        $options = $this->getOptions();
        $accessKey = isset($settings['access_key']) ? $this->sanitize->sanitizePassword($settings['access_key']) : '';
        $secretKey = isset($settings['secret_key']) ? $this->sanitize->sanitizePassword($settings['secret_key']) : '';
        $region = isset($settings['region']) ? $this->sanitize->sanitizeString($settings['region']) : '';
        $location = isset($settings['location']) ? $this->sanitize->sanitizePath($settings['location']) : '';
        $backupsToKeep = isset($settings['max_backups_to_keep']) ? $this->sanitize->sanitizeInt($settings['max_backups_to_keep']) : 2;
        $backupsToKeep = $backupsToKeep > 0 ? $backupsToKeep : 15;

        $options['location'] = $location;
        $options['region'] = $region;
        $options['accessKey'] = $accessKey;
        $options['secretKey'] = $secretKey;
        $options['maxBackupsToKeep'] = $backupsToKeep;

        $options['isAuthenticated'] = false;

        $client = $this->getClient($options);
        if ($client !== false) {
            $options['isAuthenticated'] = true;
        }

        $options['lastUpdated'] = time();

        return $this->saveOptions($options);
    }

    /**
     * Revoke both access and refresh token,
     * Also unauthenticate the provider
     */
    public function revoke()
    {
        $options = $this->getOptions();

        // Early bail if already unauthenticated
        if ($options['isAuthenticated'] === false) {
            return true;
        }

        $options['isAuthenticated'] = false;
        $options['accessKey'] = '';
        $options['secretKey'] = '';
        $options['region']    = '';
        $options['location']  = '';

        return parent::saveOptions($options);
    }

    /**
     * Delete all backup files
     * Used by /tests/webdriverNew/Backup/AmazonS3UploadCest.php
     * @return void
     */
    public function cleanBackups()
    {
        $options = $this->getOptions();
        $client = $this->getClient($options);
        if ($client === false) {
            return;
        }

        list($bucketName, $path) = $this->getLocation();

        try {
            $searchParams = [
                'Bucket' => $bucketName,
            ];

            if (!empty($path)) {
                $searchParams['Prefix'] = $path;
            }

            $result = $client->listObjects($searchParams);

            $backups = $result['Contents'];

            if (empty($backups)) {
                return;
            }

            $objects = [];
            foreach ($backups as $object) {
                $objects[] = [
                    'Key' => $object['Key']
                ];
            }

            $client->deleteObjects([
                'Bucket' => $bucketName,
                'Delete' => [
                    'Objects' => $objects,
                ],
            ]);
        } catch (Exception $ex) {
            debug_log('Error listing S3 objects: ' . $ex->getMessage());
            return;
        }
    }

    /**
     * @param $backupFile
     * @return bool
     */
    public function isBackupUploaded($backupFile)
    {
        $options = $this->getOptions();
        $client = $this->getClient($options);
        if ($client === false) {
            return false;
        }

        list($bucketName, $path) = $this->getLocation();

        try {
            $searchParams = [
                'Bucket' => $bucketName,
            ];

            $prefix = '';
            if (!empty($path)) {
                $prefix = $path;
                $searchParams['Prefix'] = $prefix;
            }

            $result = $client->listObjects($searchParams);

            $objects = $result['Contents'];

            foreach ($objects as $object) {
                if ($prefix . $backupFile === $object['Key']) {
                    return true;
                }
            }
        } catch (Exception $ex) {
            debug_log($ex->getMessage());
        }

        debug_log('Could not find backup. Error: ' . $bucketName);
        return false;
    }

    /** @return array Bucket Name of 0 index and rest of path on 1 index */
    public function getLocation()
    {
        $options = $this->getOptions();
        $location = $this->explodeLocation($options['location']);
        $bucketName = $location[0];
        $path = '';
        for ($i = 1; $i < count($location); $i++) {
            $path .= $location[$i] . '/';
        }

        return [$bucketName, $path];
    }

    /**
     * Return list of regions supported by Amazon S3
     * @return array
     *
     * @todo Refactor to use API when Amazon S3 provide an API to fetch regions
     */
    public function getRegions()
    {
        return [
            'us-east-1'      => 'US East (N. Virginia)',
            'us-east-2'      => 'US East (Ohio)',
            'us-west-1'      => 'US West (N. California)',
            'us-west-2'      => 'US West (Oregon)',
            'ap-east-1'      => 'Asia Pacific (Hong Kong)',
            'ap-south-1'     => 'Asia Pacific (Mumbai)',
            'ap-southeast-1' => 'Asia Pacific (Singapore)',
            'ap-southeast-2' => 'Asia Pacific (Sydney)',
            'ap-southeast-3' => 'Asia Pacific (Jakarta)',
            'ap-northeast-1' => 'Asia Pacific (Tokyo)',
            'ap-northeast-2' => 'Asia Pacific (Seoul)',
            'ap-northeast-3' => 'Asia Pacific (Osaka)',
            'ca-central-1'   => 'Canada (Central)',
            'eu-central-1'   => 'Europe (Frankfurt)',
            'eu-west-1'      => 'Europe (Ireland)',
            'eu-west-2'      => 'Europe (London)',
            'eu-west-3'      => 'Europe (Paris)',
            'eu-north-1'     => 'Europe (Stockholm)',
            'me-south-1'     => 'Middle East (Bahrain)',
            'sa-east-1'      => 'South America (São Paulo)',
        ];
    }

    /**
     * Get configuration object
     *
     * @return array
     */
    protected function getConfigOptions($accessKey, $secretKey, $region)
    {
        return [
            'version'     => self::S3_VERSION,
            'region'      => $region,
            'http'        => [
                'verify' => WPSTG_PLUGIN_DIR . 'Pro/Backup/cacert.pem'
            ],
            'credentials' => [
                'key'    => $accessKey,
                'secret' => $secretKey,
            ],
        ];
    }

    private function explodeLocation($location)
    {
        return explode('/', $location);
    }
}
