<?php
namespace SmartSyncS3\Ajax;
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
use Automattic\WooCommerce\Blocks\Domain\Services\GoogleAnalytics;
use Aws\Exception\AwsException;
use Aws\S3\S3Client;
use Exception;
use SmartSyncS3\Helpers\SMARL_AwsHelper;
use SmartSyncS3\Helpers\SMARL_Helper;
use SmartSyncS3\Models\SMARL_CloudConfig;


/**
 * Class AwsConfigAjax
 * Handles AJAX requests for managing AWS configurations, including S3 bucket setup,
 * CloudFront distributions, and SSL certificate generation/validation.
 *
 *
 * @package SmartSyncS3
 * @author Infinitie Technologies
 *
 * Credits:
 * - Developed by Infinitie Technologies
 * - Maintained by the Infintitie Development Team
 * - Inspired by best practices for WordPress plugin development
 *
 */
class SMARL_AwsConfigAjax
{
    /**
     * @var SMARL_CloudConfig|null $appConfig AWS Cloud configuration object.
     */
    private ?SMARL_CloudConfig $appConfig;

    /**
     * AwsConfigAjax constructor.
     * Initializes the object with the given configuration and registers AJAX actions.
     *
     * @param SMARL_CloudConfig|null $configuration The AWS Cloud configuration object.
     * @developer Jay Parmar <infinitie.jay@gmail.com>
     */
    public function __construct(?SMARL_CloudConfig $configuration)
    {
        if($configuration){
            $this->appConfig = $configuration;
        }

    }


    public function updateCloudFrontAlternateDomain(): void {
        try {


            $certificateArn = $this->appConfig->customUrl->certificateArn;
            $alternateDomain = $this->appConfig->customUrl->domain;
            $distributionId = $this->appConfig->distributionId;


            $cnameValue = SMARL_Helper::checkCNAME($alternateDomain);

            if($cnameValue === null || $cnameValue !== $this->appConfig->cloudfrontUrl){
                wp_send_json_error(esc_html__("Please validate all the records before attaching it to cloudfront", 'smartsync-lite-media-offloader-and-assets-cdn'));
            }


            // Initialize the CloudFront client
            $cloudFront = $this->appConfig->getCloudfrontClient();

            // Get the current distribution configuration
            $result = $cloudFront->getDistributionConfig([
                'Id' => $distributionId,
            ]);

            $distributionConfig = $result['DistributionConfig'];
            $eTag = $result['ETag'];

            // Add the alternate domain to the CNAMEs
            $distributionConfig['Aliases']['Quantity'] += 1;
            $distributionConfig['Aliases']['Items'][] = $alternateDomain;

            // Add the certificate configuration
            $distributionConfig['ViewerCertificate'] = [
                'ACMCertificateArn' => $certificateArn,
                'SSLSupportMethod' => 'sni-only', // Use SNI-based SSL for custom domain
                'MinimumProtocolVersion' => 'TLSv1.2_2021'
            ];

            // Update the distribution with the new configuration
            $cloudFront->updateDistribution([
                'Id' => $distributionId,
                'IfMatch' => $eTag, // Required for conditional updates
                'DistributionConfig' => $distributionConfig,
            ]);
            $this->appConfig->url = $this->appConfig->customUrl->domain;
            $this->appConfig->save();
            wp_send_json_success(esc_html__("CloudFront distribution updated successfully with the alternate domain and certificate.", 'smartsync-lite-media-offloader-and-assets-cdn'));

        } catch (AwsException $e) {
            wp_send_json_error($e->getAwsErrorMessage());
            // Handle AWS SDK errors
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
            // Handle general errors
        }
    }


    /**
     * Registering AJAX and scheduled actions
     *
     * @developer Jay Parmar <infinitie.jay@gmail.com>
     */
    public function initializeActions(): void
    {
        add_action('wp_ajax_check_dns_record', [$this, 'checkDnsRecord']);
        add_action('wp_ajax_generate_ssl_certificate', [$this, 'generateSslCertificate']);
        add_action('wp_ajax_list_s3_buckets', [$this, 'listS3Buckets']);
        add_action('wp_ajax_configure_aws', [$this, 'configureAws']);
        add_action('wp_ajax_attach_to_cloudfront', [$this, 'updateCloudFrontAlternateDomain']);
    }


    /**
     * Configures AWS services including S3 buckets and CloudFront distributions.
     * Handles creating or selecting S3 buckets, and optionally setting up CloudFront with Origin Access Control (OAC).
     *
     * @throws Exception If an error occurs during configuration.
     * @developer Jay Parmar <infinitie.jay@gmail.com>
     *
     */

    private function validateAwsConfiguration($bucketName, $service_provider, $iamSecret, $iamKey, $bucketType, $bucketRegion): void
    {
        if (empty($service_provider) ){
            wp_send_json_error(esc_html__('service provider is required', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }

        if (!preg_match('/^[a-z0-9.-]{3,63}$/', $bucketName)) {
            wp_send_json_error(esc_html__('Invalid bucket name. Must follow AWS bucket naming conventions', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }




        if ($bucketType === 'existing' && empty($bucketName)) {
            wp_send_json_error(esc_html__('Please select an existing bucket', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }

        if ($bucketType === 'new' && (empty($bucketName) || empty($bucketRegion))) {
            wp_send_json_error(esc_html__('Please enter a bucket name and select a region for the new bucket', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }
    }

    /**
     * Creates a new bucket in AWS S3.
     *
     * @param S3Client $s3Client S3 client instance.
     * @param string $bucketName Name of the bucket to create.
     * @param string $bucketRegion Region where the bucket will be created.
     */
    private function createAWSBucket(S3Client $s3Client, string $bucketName, string $bucketRegion): void
    {
        $bucketOptions = [
            'Bucket' => $bucketName,
        ];

        if ($bucketRegion !== "us-east-1") {
            $bucketOptions['CreateBucketConfiguration'] = [
                'LocationConstraint' => $bucketRegion,
            ];
        }

        $s3Client->createBucket($bucketOptions);
        $s3Client->waitUntil('BucketExists', ['Bucket' => $bucketName]);
    }

    /**
     * Creates a new space in DigitalOcean.
     *
     * @param string $bucketName Name of the space to create.
     * @param string $bucketRegion Region where the space will be created.
     */
    private function createDigitalOceanSpace(string $bucketName, string $bucketRegion): void
    {
        $this->appConfig->region = $bucketRegion;
        $data = $this->appConfig->createDigitalOceanSpace(spaceName: $bucketName);

        if (!$data["success"]) {
            if ($data['code'] === 409) {
                wp_send_json_error(esc_html__("Bucket name not available. Try a different bucket name.", 'smartsync-lite-media-offloader-and-assets-cdn'));
            }
            wp_send_json_error($data["error"]);
        }
    }

    /**
     * @throws Exception
     */
    public function configureAws(): void
    {
        check_ajax_referer('smarl_nonce', 'nonce');

        $bucketType = isset($_POST['bucket_type']) ? sanitize_text_field(wp_unslash($_POST['bucket_type'])) : '';
        $iamKey = isset($_POST['iam_key']) ? sanitize_text_field(wp_unslash($_POST['iam_key'])) : '';
        $iamSecret = isset($_POST['iam_secret']) ? sanitize_text_field(wp_unslash($_POST['iam_secret'])) : '';
        $bucketName = isset($_POST['bucket_name']) ? sanitize_text_field(wp_unslash($_POST['bucket_name'])) : '';
        $bucketRegion = isset($_POST['bucket_region']) ? sanitize_text_field(wp_unslash($_POST['bucket_region'])) : '';
        $useCloudFront = isset($_POST['use_cloudfront']) && filter_var(wp_unslash($_POST['use_cloudfront']), FILTER_VALIDATE_BOOLEAN);
        $service_provider = isset($_POST['service_provider']) ? sanitize_text_field(wp_unslash($_POST['service_provider'])) : '';
        $cloudflareEndpoint = isset($_POST['cloudflare_endpoint']) ? sanitize_url(($_POST['cloudflare_endpoint'])) : '';

        try {
            $this->validateAwsConfiguration(
                bucketName: $bucketName,
                service_provider: $service_provider,
                iamSecret: $iamSecret,
                iamKey: $iamKey,
                bucketType: $bucketType,
                bucketRegion: $bucketRegion
            );


            $this->appConfig->serviceProvider = $service_provider;

            $this->appConfig->iamKey = $iamKey;
            $this->appConfig->iamSecret = $iamSecret;


            $s3 = $this->appConfig->getS3Client(region: $bucketRegion);

            // Create the bucket
            if ($bucketType === 'new') {
                if ($service_provider === "aws") {
                    $this->createAWSBucket(s3Client: $s3, bucketName: $bucketName,bucketRegion:  $bucketRegion);
                } elseif($service_provider === "digitalocean") {
                    $this->createDigitalOceanSpace(bucketName: $bucketName,bucketRegion:  $bucketRegion);
                }
            }
            if ($useCloudFront) {
                $cloudFront = $this->appConfig->getCloudfrontClient();
                $oacResponse = $cloudFront->createOriginAccessControl(SMARL_AwsHelper::generateOACArray(bucketName: $bucketName));
                $oacId = $oacResponse['OriginAccessControl']['Id'];

                $distribution = $cloudFront->createDistribution(SMARL_AwsHelper::createCloudfrontArray(
                    bucketName: $bucketName,bucketRegion: $bucketRegion,oacId: $oacId
                ));

                $distributionId = $distribution['Distribution']['Id'];


                $stsClient = $this->appConfig->getStsClient(region: "us-east-1");

                $result = $stsClient->getCallerIdentity();



                $accountId = $result['Account'];
                $bucketPolicy = [
                    'Version' => '2012-10-17',
                    'Statement' => [
                        [
                            'Sid' => 'AllowCloudFrontServicePrincipal',
                            'Effect' => 'Allow',
                            'Principal' => [
                                'Service' => 'cloudfront.amazonaws.com'
                            ],
                            'Action' => 's3:GetObject',
                            'Resource' => "arn:aws:s3:::$bucketName/*",
                            'Condition' => [
                                'StringEquals' => [
                                    'AWS:SourceArn' => "arn:aws:cloudfront::$accountId:distribution/$distributionId"
                                ]
                            ]
                        ]
                    ]
                ];

                $s3->putBucketPolicy([
                    'Bucket' => $bucketName,
                    'Policy' => json_encode($bucketPolicy)
                ]);
                $config = new SMARL_CloudConfig();
                $config->bucket = $bucketName;
                $config->region = $bucketRegion;
                $config->cloudfrontUrl = $distribution['Distribution']['DomainName'];
                $config->url = $distribution['Distribution']['DomainName'];
                $config->iamKey = $iamKey;
                $config->iamSecret = $iamSecret;
                $config->distributionId = $distributionId;
                $config->useCloudFront = true;
                $config->urlType = 'cloudfront';
                $config->useCdnForAssets = false;
                $config->serviceProvider = $service_provider;
                $config->useCdnForMedia = false;
                $config->save();

            } else {
                $url = "$bucketName.s3.$bucketRegion.amazonaws.com";
                if($service_provider == "aws"){
                    $s3->deletePublicAccessBlock([
                        'Bucket' => $bucketName,
                    ]);

                    $s3->putBucketPolicy([
                        'Bucket' => $bucketName,
                        'Policy' => json_encode([
                            'Version' => '2012-10-17',
                            'Statement' => [
                                [
                                    'Effect' => 'Allow',
                                    'Principal' => '*',
                                    'Action' => 's3:GetObject',
                                    'Resource' => "arn:aws:s3:::$bucketName/*",
                                ]
                            ]
                        ]),
                    ]);

                    $s3->putPublicAccessBlock([
                        'Bucket' => $bucketName,
                        'PublicAccessBlockConfiguration' => [
                            'BlockPublicAcls' => false,
                            'IgnorePublicAcls' => false,
                            'BlockPublicPolicy' => false,
                            'RestrictPublicBuckets' => false,
                        ],
                    ]);
                }
                elseif($service_provider == "digitalocean"){
                    $url = "$bucketName.$bucketRegion.digitaloceanspaces.com";
                }elseif($service_provider == "wasabi"){
                    $url = "s3.$bucketRegion.wasabisys.com/$bucketName";
                }elseif($service_provider == "google"){
                    $url = "https://storage.googleapis.com/$bucketName";
                }elseif($service_provider == "cloudflare"){
                    $url = isset( $_POST['cloudflare_url'] ) ? esc_url_raw( wp_unslash( $_POST['cloudflare_url'] ) ) : '';

                    $url = preg_replace('/^https?:\/\//', '', $url);

                    $url = rtrim($url, '/');

                    $this->appConfig->cloudflareUrl = $url;
                }
                $config = new SMARL_CloudConfig();
                $config->googleServicePath = "";
                $config->bucket = $bucketName;
                $config->region = $bucketRegion;
                $config->cloudfrontUrl = "";
                $config->url = $url;
                $config->iamKey = $iamKey;
                $config->iamSecret = $iamSecret;
                $config->distributionId = "";
                $config->useCloudFront = false;
                $config->urlType = "s3";
                $config->serviceProvider = $service_provider;
                $config->cloudflareEndpoint = $cloudflareEndpoint;

                $config->save();
                // Save the configuration to the WordPress database
            }


            wp_send_json_success(esc_html__('AWS configuration saved successfully.', 'smartsync-lite-media-offloader-and-assets-cdn'));
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }

    /**
     * Lists all S3 buckets accessible with the provided IAM credentials.
     * Retrieves the bucket names and their respective regions.
     *
     * @throws Exception If an error occurs while fetching the bucket list.
     * @developer Jay Parmar <infinitie.jay@gmail.com>
     */
    public function listS3Buckets(): void
    {
        check_ajax_referer('smarl_nonce', 'nonce');



        $iam_key = isset($_POST['iam_key']) ? sanitize_text_field(wp_unslash($_POST['iam_key'])) : '';
        $iam_secret = isset($_POST['iam_secret']) ? sanitize_text_field(wp_unslash($_POST['iam_secret'])) : ''; // Avoid sanitize_text_field for secrets
        $service_provider = isset($_POST['service_provider']) ? sanitize_text_field(wp_unslash($_POST['service_provider'])) : '';




        if (empty($service_provider) ){
            wp_send_json_error(esc_html__('service provider is required', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }
        if (empty($iam_key) || empty($iam_secret)) {
            wp_send_json_error(esc_html__('IAM Key and Secret cannot be empty.', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }

        try {

            $this->appConfig->iamKey = $iam_key;
            $this->appConfig->iamSecret = $iam_secret;
            $this->appConfig->serviceProvider = $service_provider;
            if ($service_provider == "aws"){
                $s3 = $this->appConfig->getS3Client(region: 'ap-south-1');
            }elseif($service_provider == "digitalocean"){
                $s3 = $this->appConfig->getS3Client(region: 'nyc3');
            }elseif ($service_provider == "wasabi"){
                $s3 = $this->appConfig->getS3Client(region: 'us-central-1');
            }
            elseif ($service_provider == "cloudflare"){
                if ( isset( $_POST['cloudflare_endpoint'] ) ) {
                    $cloudflare_endpoint = sanitize_text_field( $_POST['cloudflare_endpoint'] );

                    // Optional: Validate as URL or hostname if needed
                    if ( filter_var( $cloudflare_endpoint, FILTER_VALIDATE_URL ) ) {
                        $this->appConfig->cloudflareEndpoint = esc_url_raw( $cloudflare_endpoint );
                    } else {
                        // Handle invalid input: log or show admin notice
                    }
                }

                $s3 = $this->appConfig->getS3Client(region: 'us-central-1');
            }

            $buckets = $s3->listBuckets();

            $bucketNames = [];
            foreach ($buckets['Buckets'] as $bucket) {
                try {

                    $location = $s3->getBucketLocation([
                        'Bucket' => $bucket['Name']
                    ]);

                    $bucketNames[] = $bucket['Name'] . "|" . wp_strip_all_tags($location['LocationConstraint']) ?: 'us-east-1';

                } catch (Exception) {
                    // Handle any errors accessing bucket location
                    continue;
                }
            }


            wp_send_json_success(['buckets' => $bucketNames]);
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }

    /**
     * Generates an SSL certificate for a specified domain using AWS ACM.
     * Provides DNS records for certificate validation and updates options for the certificate.
     *
     * @throws Exception If an error occurs during certificate request or retrieval.
     * @developer Jay Parmar <infinitie.jay@gmail.com>
     */
    public function generateSslCertificate(): void
    {
        check_ajax_referer('smarl_nonce', 'nonce');
        $this->appConfig->customUrl->reset();

        $iamKey = $this->appConfig->iamKey;
        $iamSecret = $this->appConfig->iamSecret;

        $domain = '';
            $raw_domain = isset( $_POST['domain'] ) ? sanitize_text_field( wp_unslash( $_POST['domain'] ) ) : '';
            $sanitized_domain = esc_url_raw($raw_domain); // Safely strips tags and encodes characters

            // Optional: Validate if it's a proper domain/URL (change based on your exact need)
            if (filter_var($sanitized_domain, FILTER_VALIDATE_URL) || filter_var($sanitized_domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
                $domain = $sanitized_domain;
            }



        if (empty($iamKey) || empty($iamSecret)) {
            wp_send_json_error(esc_html__('IAM Key and Secret are required.', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }

        if (empty($domain)) {
            wp_send_json_error(esc_html__('Domain is required.', 'smartsync-lite-media-offloader-and-assets-cdn'));
        }

        try {
            $acm = $this->appConfig->getAcmClient();

            // Request a new certificate
            $result = $acm->requestCertificate([
                'DomainName' => $domain,
                'ValidationMethod' => 'DNS',
                'IdempotencyToken' => (string) time(),
                'DomainValidationOptions' => [
                    [
                        'DomainName' => $domain,
                        'ValidationDomain' => $domain,
                    ],
                ],
                'Options' => [
                    'CertificateTransparencyLoggingPreference' => 'ENABLED',
                ],
            ]);

            // Fetch the CertificateArn
            $certificateArn = $result['CertificateArn'];

            // Retry mechanism to fetch DNS validation records
            $records = [];
            $maxRetries = 10; // Maximum number of retries
            $retryInterval = 5; // Wait time between retries in seconds
            $attempts = 0;

            do {
                $certificateDetails = $acm->describeCertificate([
                    'CertificateArn' => $certificateArn,
                ]);

                $validationOptions = $certificateDetails['Certificate']['DomainValidationOptions'];

                foreach ($validationOptions as $option) {
                    if (!empty($option['ResourceRecord'])) {
                        $records[] = [
                            'DomainName' => $option['DomainName'],
                            'Type' => $option['ResourceRecord']['Type'],
                            'Name' => $option['ResourceRecord']['Name'],
                            'Value' => $option['ResourceRecord']['Value'],
                            'systemGenerated' => false
                        ];
                    }
                }

                if (!empty($records)) {
                    break; // Exit loop if records are found
                }

                sleep($retryInterval); // Wait before next attempt
                $attempts++;
            } while ($attempts < $maxRetries);

            if (empty($records)) {
                wp_send_json_error(esc_html__('DNS records are not yet available. Please try again later.', 'smartsync-lite-media-offloader-and-assets-cdn'));
            }

            $records[] = [
                'DomainName' => $domain,
                'Type' => "CNAME",
                'Name' => $option['DomainName'],
                'Value' => $this->appConfig->cloudfrontUrl,
                'systemGenerated' => true
            ];


            $this->appConfig->customUrl->certificateArn = $certificateArn;
            $this->appConfig->customUrl->domain = $domain;
            $this->appConfig->customUrl->records = $records;
            $this->appConfig->customUrl->save();


            // Return the CertificateArn and DNS records
            wp_send_json_success([
                'message' => esc_html__('SSL certificate requested successfully. Add the following DNS records for validation.', 'smartsync-lite-media-offloader-and-assets-cdn'),
                'certificateArn' => $certificateArn,
                'records' => $records,
            ]);
        } catch (Exception $e) {

            wp_send_json_error(  $e->getMessage());
        }
    }

    /**
     * Checks the DNS validation status of an SSL certificate using its ARN.
     * Verifies whether the required DNS records have been added and validated.
     *
     * @throws Exception If an error occurs during the validation check.
     * @developer Jay Parmar <infinitie.jay@gmail.com>
     */
    public function checkDnsRecord(): void{

        check_ajax_referer('smarl_nonce', 'nonce');

        $certificateArn = '';
        if (isset($_POST['certificateArn'])) {
            $sanitized_arn = isset( $_POST['certificateArn'] ) ? sanitize_text_field( wp_unslash( $_POST['certificateArn'] ) ) : '';

            if (preg_match('/^arn:aws:[a-z\-]+:[a-z0-9\-]+:\d{12}:.+$/', $sanitized_arn)) {
                $certificateArn = $sanitized_arn;
            } else {
                wp_send_json_error(['message' => 'Invalid certificate ARN.']);
                wp_die();
            }
        }
        $iamKey = $this->appConfig->iamKey;
        $iamSecret = $this->appConfig->iamSecret;

        if (empty($certificateArn) || empty($iamKey) || empty($iamSecret)) {
            wp_send_json_error(esc_html__("Invalid input parameters.", 'smartsync-lite-media-offloader-and-assets-cdn'));
        }

        try {

            // Initialize ACM client
            $acm = $this->appConfig->getAcmClient();


            try {
                $certificateDetails = $acm->describeCertificate([
                    'CertificateArn' => $certificateArn,
                ]);
            } catch (Exception) {
                // Handle any errors accessing bucket location
                $this->appConfig->customUrl->reset();
                wp_send_json_error([
                    'message' =>esc_html__( 'Certificate not found. Please request a new certificate.', 'smartsync-lite-media-offloader-and-assets-cdn'),
                    'error' => true,
                ]);
                return;
            }


            // Check validation options
            $validationOptions = $certificateDetails['Certificate']['DomainValidationOptions'];
            $allVerified = true;
            $records = [];

            foreach ($validationOptions as $option) {
                $recordStatus = [
                    'DomainName' => $option['DomainName'],
                    'ValidationStatus' => $option['ValidationStatus'], // PENDING_VALIDATION, SUCCESS, etc.
                ];

                if (isset($option['ResourceRecord'])) {
                    $recordStatus['Type'] = $option['ResourceRecord']['Type'];
                    $recordStatus['Name'] = $option['ResourceRecord']['Name'];
                    $recordStatus['Value'] = $option['ResourceRecord']['Value'];
                }

                if ($option['ValidationStatus'] !== 'SUCCESS') {
                    $allVerified = false;
                }

                $records[] = $recordStatus;
            }

            if ($allVerified) {
                wp_send_json_success(['verified' => true, 'records' => $records]);
            } else {
                wp_send_json_success(['verified' => false, 'records' => $records]);
            }
        } catch (Exception $e) {
            wp_send_json_error(  $e->getMessage());
        }
    }
}






