<?php
/**
 * SureForms Pro Integrations Database Table Class.
 *
 * @link       https://sureforms.com
 * @since      1.13.0
 * @package    sureforms-pro
 */

namespace SRFM_Pro\Inc\Pro\Database\Tables;

use SRFM\Inc\Database\Base;
use SRFM\Inc\Helper;
use SRFM\Inc\Traits\Get_Instance;
use SRFM_Pro\Inc\Pro\Native_Integrations\Encryption;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * SureForms Pro Integrations Database Table Class.
 *
 * @since 1.13.0
 */
class Integrations extends Base {
	use Get_Instance;

	/**
	 * {@inheritDoc}
	 *
	 * @var string
	 */
	protected $table_suffix = 'integrations';

	/**
	 * {@inheritDoc}
	 *
	 * @var int
	 */
	protected $table_version = 1;

	/**
	 * {@inheritDoc}
	 */
	public function get_schema() {
		return [
			// Integration ID.
			'id'         => [
				'type' => 'number',
			],
			// Integration type/slug.
			'type'       => [
				'type' => 'string',
			],
			// Integration display name.
			'name'       => [
				'type' => 'string',
			],
			// Integration configuration data (JSON).
			'data'       => [
				'type'    => 'array',
				'default' => [],
			],
			// Integration status (1 = enabled, 0 = disabled).
			'status'     => [
				'type'    => 'boolean',
				'default' => false,
			],
			// Creation timestamp.
			'created_at' => [
				'type' => 'datetime',
			],
			// Last update timestamp.
			'updated_at' => [
				'type' => 'datetime',
			],
		];
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_columns_definition() {
		return [
			'id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY',
			'type VARCHAR(100) NOT NULL',
			'name VARCHAR(100)',
			'data LONGTEXT',
			'status TINYINT(1) DEFAULT 0',
			'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',
			'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
			'UNIQUE KEY idx_type (type)',
			'INDEX idx_status (status)',
		];
	}

	/**
	 * Add a new integration to the database.
	 *
	 * @param array<mixed> $data An associative array of data for the new integration. Must include 'type'.
	 *                    If 'id' is set, it will be removed before inserting.
	 * @since 1.13.0
	 * @return int|false The number of rows inserted, or false if the insertion fails.
	 */
	public static function add( $data ) {
		if ( ! is_array( $data ) || empty( $data['type'] ) ) {
			return false;
		}

		if ( isset( $data['id'] ) ) {
			// Unset ID if exists because we are creating a new entry, not updating.
			unset( $data['id'] );
		}

		// Encrypt sensitive data before storing.
		if ( isset( $data['data'] ) && is_array( $data['data'] ) ) {
			$data['data'] = self::encrypt_sensitive_data( $data['data'] );
		}

		return self::get_instance()->use_insert( $data );
	}

	/**
	 * Update an integration by integration id.
	 *
	 * @param int                 $integration_id Integration ID.
	 * @param array<string,mixed> $data           Data to update.
	 * @since 1.13.0
	 * @return int|false The number of rows updated, or false on error.
	 */
	public static function update( $integration_id, $data = [] ) {
		if ( empty( $integration_id ) ) {
			return false;
		}

		// Encrypt sensitive data before storing.
		if ( isset( $data['data'] ) && is_array( $data['data'] ) ) {
			$data['data'] = self::encrypt_sensitive_data( $data['data'] );
		}

		return self::get_instance()->use_update( $data, [ 'id' => absint( $integration_id ) ] );
	}

	/**
	 * Delete an integration by integration id.
	 *
	 * @param int $integration_id Integration ID to delete.
	 * @since 1.13.0
	 * @return int|false The number of rows deleted, or false on error.
	 */
	public static function delete( $integration_id ) {
		return self::get_instance()->use_delete( [ 'id' => absint( $integration_id ) ], [ '%d' ] );
	}

	/**
	 * Retrieve a specific integration from the database.
	 *
	 * @param int $integration_id The ID of the integration to retrieve.
	 * @since 1.13.0
	 * @return array<mixed> An associative array representing the integration, or an empty array if no integration is found.
	 */
	public static function get( $integration_id ) {
		if ( ! is_numeric( $integration_id ) || $integration_id <= 0 ) {
			return [];
		}

		return self::get_by( 'id', Helper::get_integer_value( $integration_id ) );
	}

	/**
	 * Get an integration by type.
	 *
	 * @param string $type Integration type (e.g., 'mailchimp', 'brevo').
	 * @since 1.13.0
	 * @return array<mixed> Integration data or empty array if not found.
	 */
	public static function get_by_type( $type ) {
		if ( ! is_string( $type ) || '' === trim( $type ) || empty( $type ) ) {
			return [];
		}

		return self::get_by( 'type', Helper::get_string_value( $type ) );
	}

	/**
	 * Retrieves a list of integrations based on the provided arguments.
	 *
	 * This method fetches results from the database, allowing for various
	 * customization options such as filtering, pagination, and sorting.
	 *
	 * @param array<string,mixed> $args {
	 *     Optional. An array of arguments to customize the query.
	 *
	 *     @type array  $where   An associative array of conditions to filter the results.
	 *     @type int    $limit   The maximum number of results to return. Default is 10.
	 *     @type int    $offset  The number of records to skip before starting to collect results. Default is 0.
	 *     @type string $orderby  The column by which to order the results. Default is 'created_at'.
	 *     @type string $order    The direction of the order (ASC or DESC). Default is 'DESC'.
	 * }
	 * @param bool                $set_limit Whether to set the limit on the query. Default is true.
	 * @param bool                $decrypt_data Whether to decrypt sensitive data. Default is false for performance.
	 *
	 * @since 1.13.0
	 * @return array<mixed> The results of the query, typically an array of objects or associative arrays.
	 */
	public static function get_all( $args = [], $set_limit = true, $decrypt_data = false ) {
		/**
		 * List of integration results.
		 *
		 * @var array<int, array<string, mixed>> $results result of the integrations */
		$results = self::get_instance()->get_records_by_args( $args, $set_limit );

		// Decrypt sensitive data if requested.
		if ( $decrypt_data ) {
			foreach ( $results as $key => $integration ) {
				if ( is_array( $integration ) && ! empty( $integration['data'] ) && is_array( $integration['data'] ) ) {
					$results[ $key ]['data'] = self::decrypt_sensitive_data( $integration['data'] );
				}
			}
		}

		return $results;
	}

	/**
	 * Get all enabled integrations.
	 *
	 * @param bool $decrypt_data Whether to decrypt sensitive data. Default is false for performance.
	 * @since 1.13.0
	 * @return array<mixed> Array of enabled integrations.
	 */
	public static function get_enabled( $decrypt_data = false ) {
		return self::get_all(
			[
				'where' => [
					'status' => 1,
				],
			],
			false,
			$decrypt_data
		);
	}

	/**
	 * Get the total count of integrations by status.
	 *
	 * @param string              $status The status of the integrations to count ('all', 'enabled', 'disabled').
	 * @param array<string,mixed> $where_clause Additional where clause to add to the query.
	 * @since 1.13.0
	 * @return int The total number of integrations with the specified status.
	 */
	public static function get_total_integrations_by_status( $status = 'all', $where_clause = [] ) {
		switch ( $status ) {
			case 'all':
				return self::get_instance()->get_total_count( $where_clause );
			case 'enabled':
				$where_clause[] = [
					[
						'key'     => 'status',
						'compare' => '=',
						'value'   => 1,
					],
				];
				return self::get_instance()->get_total_count( $where_clause );
			case 'disabled':
				$where_clause[] = [
					[
						'key'     => 'status',
						'compare' => '=',
						'value'   => 0,
					],
				];
				return self::get_instance()->get_total_count( $where_clause );
			default:
				return self::get_instance()->get_total_count();
		}
	}

	/**
	 * Save integration configuration.
	 *
	 * @param string      $type Integration type.
	 * @param string      $name Integration display name.
	 * @param array       $data Configuration data.
	 * @param bool|string $status Integration status (true/1/'enabled' for enabled, false/0/'disabled' for disabled).
	 * @since 1.13.0
	 * @return int|false Integration ID on success, false on failure.
	 */
	public static function save_integration( $type, $name, $data, $status = false ) {
		// Check if integration already exists.
		$existing = self::get_by_type( $type );

		// Convert status to boolean/integer.
		if ( is_string( $status ) ) {
			$status = 'enabled' === $status ? 1 : 0;
		} else {
			$status = $status ? 1 : 0;
		}

		$integration_data = [
			'type'   => $type,
			'name'   => $name,
			'data'   => $data,
			'status' => $status,
		];

		if ( ! empty( $existing ) ) {
			// Update existing integration.
			return self::update( Helper::get_integer_value( $existing['id'] ), $integration_data );
		}

		// Insert new integration.
		return self::add( $integration_data );
	}

	/**
	 * Delete an integration by type.
	 *
	 * @param string $type Integration type.
	 * @since 1.13.0
	 * @return bool true on success, false on failure.
	 */
	public static function delete_by_type( $type ) {
		$existing = self::get_by_type( $type );

		if ( empty( $existing ) ) {
			return false;
		}

		return false !== self::delete( Helper::get_integer_value( $existing['id'] ) );
	}

	/**
	 * Update integration status.
	 *
	 * @param string      $type Integration type.
	 * @param bool|string $status New status (true/1/'enabled' for enabled, false/0/'disabled' for disabled).
	 * @since 1.13.0
	 * @return bool true on success, false on failure.
	 */
	public static function update_status( $type, $status ) {
		$existing = self::get_by_type( $type );

		if ( empty( $existing ) ) {
			return false;
		}

		// Convert status to boolean/integer.
		if ( is_string( $status ) ) {
			$status = 'enabled' === $status ? 1 : 0;
		} else {
			$status = $status ? 1 : 0;
		}

		return false !== self::update(
			Helper::get_integer_value( $existing['id'] ),
			[
				'status' => $status,
			]
		);
	}

	/**
	 * Encrypt sensitive data before storing.
	 *
	 * @param array $data Data to encrypt.
	 * @since 1.13.0
	 * @return array Data with sensitive fields encrypted.
	 */
	public static function encrypt_sensitive_data( $data ) {
		// Define sensitive fields that should be encrypted.
		$sensitive_fields = [ 'api_key', 'api_secret', 'token', 'password', 'client_secret', 'access_token', 'refresh_token' ];

		$encrypted_data = [];

		foreach ( $data as $key => $value ) {
			if ( in_array( $key, $sensitive_fields, true ) && ! empty( $value ) ) {
				// Encrypt sensitive fields using our secure encryption class.
				/**
				 * Static call is handled by __callStatic() magic method in Encryption class.
				 * PHPStan doesn't recognize magic method calls to protected methods.
				 *
				 * @phpstan-ignore-next-line
				 */
				$encrypted_data[ $key ] = Encryption::encrypt( $value );
			} else {
				// Non-sensitive fields are stored as-is.
				$encrypted_data[ $key ] = $value;
			}
		}

		return $encrypted_data;
	}

	/**
	 * Decrypt sensitive data after retrieving.
	 *
	 * @param array $data Data to decrypt.
	 * @since 1.13.0
	 * @return array Data with sensitive fields decrypted.
	 */
	public static function decrypt_sensitive_data( $data ) {
		// Define sensitive fields that should be decrypted.
		$sensitive_fields = [ 'api_key', 'api_secret', 'token', 'password', 'client_secret', 'access_token', 'refresh_token' ];

		$decrypted_data = [];

		foreach ( $data as $key => $value ) {
			if ( in_array( $key, $sensitive_fields, true ) && ! empty( $value ) ) {
				// Decrypt the value using our secure encryption class.
				/**
				 * Static call is handled by __callStatic() magic method in Encryption class.
				 * PHPStan doesn't recognize magic method calls to protected methods.
				 *
				 * @phpstan-ignore-next-line
				 */
				$decrypted_data[ $key ] = Encryption::decrypt( $value );
			} else {
				// Non-sensitive fields are returned as-is.
				$decrypted_data[ $key ] = $value;
			}
		}

		return $decrypted_data;
	}

	/**
	 * Retrieve integration data by a given condition.
	 *
	 * @param string     $key Lookup key (e.g., 'id', 'type').
	 * @param int|string $value Lookup value (e.g., 1, 'mailchimp').
	 * @return array<mixed> Integration data or empty array if not found.
	 */
	private static function get_by( $key, $value ) {
		if ( empty( $key ) || empty( $value ) ) {
			return [];
		}

		$results = self::get_instance()->get_results(
			[
				$key => $value,
			]
		);

		$integration = isset( $results[0] ) && is_array( $results[0] ) ? Helper::get_array_value( $results[0] ) : [];

		if ( ! empty( $integration['data'] ) && is_array( $integration['data'] ) ) {
			$integration['data'] = self::decrypt_sensitive_data( $integration['data'] );
		}

		return $integration;
	}
}
