<?php

namespace App\Database\Migrations;

use App\Models\Appconfig;
use CodeIgniter\Database\Forge;
use CodeIgniter\Database\Migration;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use Config\Encryption;
use Config\Services;
use ReflectionException;

class Convert_to_ci4 extends Migration
{
	/**
	 * Constructor.
	 */
	public function __construct(?Forge $forge = null)
	{
		parent::__construct($forge);
		helper('security');
	}

	/**
	 * Perform a migration step.
	 */
	public function up(): void
	{
		error_log('Migrating database to CodeIgniter4 formats');

		helper('migration');
		execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_ci4_conversion.sql');

		if(!empty(config('Encryption')->key))
		{
			$this->convert_ci3_encrypted_data();
		}
		else
		{
			check_encryption();
		}

		remove_backup();

		error_log('Migrating to CodeIgniter4 formats completed');
	}

	/**
	 * Revert a migration step.
	 */
	public function down(): void
	{

	}

	/**
	 * @return RedirectResponse|void
	 * @throws ReflectionException
	 */
	private function convert_ci3_encrypted_data()
	{
		$appconfig = model(Appconfig::class);

		$ci3_encrypted_data = [
			'clcdesq_api_key' => '',
			'clcdesq_api_url' => '',
			'mailchimp_api_key' => '',
			'mailchimp_list_id' => '',
			'smtp_pass' => ''
		];

		foreach($ci3_encrypted_data as $key => $value)
		{
			$ci3_encrypted_data[$key] = $appconfig->get_value($key);
		}

		$decrypted_data = $this->decrypt_ci3_data($ci3_encrypted_data);

		check_encryption();

		try
		{
			$ci4_encrypted_data = $this->encrypt_data($decrypted_data);

			$success = empty(array_diff_assoc($decrypted_data, $this->decrypt_data($ci4_encrypted_data)));
			if(!$success)
			{
				abort_encryption_conversion();
				remove_backup();
				throw new RedirectException('login');
			}

			$appconfig->batch_save($ci4_encrypted_data);
		} catch(RedirectException $e)
		{
			return redirect()->to('login'); //TODO: Need to figure out how to pass the error to the Login controller so that it gets displayed.
		}
	}

	/**
	 * Decrypts CI3 encrypted data and returns the plaintext values.
	 *
	 * @param array $encrypted_data Data encrypted using CI3 methodology.
	 * @return array Plaintext, unencrypted data.
	 */
	private function decrypt_ci3_data(array $encrypted_data): array
	{
		$config = new Encryption();
		$config->driver = 'OpenSSL';
		$config->key = config('Encryption')->key;
		$config->cipher = 'AES-128-CBC';
		$config->rawData = false;
		$config->encryptKeyInfo = 'encryption';
		$config->authKeyInfo = 'authentication';

		$encrypter = Services::encrypter($config);

		$decrypted_data = [];
		foreach($encrypted_data as $key => $value)
		{
			$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value): '';
		}

		return $decrypted_data;
	}

	/**
	 * Encrypts data using CI4 algorithms.
	 *
	 * @param array $plain_data Data to be encrypted.
	 * @return array Encrypted data.
	 */
	private function encrypt_data(array $plain_data): array
	{
		$encrypter = Services::encrypter();

		$encrypted_data = [];
		foreach($plain_data as $key => $value)
		{
			$encrypted_data[$key] = !empty($value) ? $encrypter->encrypt($value) : '';
		}

		return $encrypted_data;
	}

	/**
	 * Decrypts data using CI4 algorithms.
	 *
	 * @param array $encrypted_data Data to be decrypted.
	 * @return array Decrypted data.
	 */
	private function decrypt_data(array $encrypted_data): array
	{
		$encrypter = Services::encrypter();

		$decrypted_data = [];
		foreach($encrypted_data as $key => $value)
		{
			$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value) : '';
		}

		return $decrypted_data;
	}
}
