<?php

namespace WC_Affiliate\Bootstrap;

defined( 'ABSPATH' ) || exit;

use WC_Affiliate\Models\Database;
use WC_Affiliate\Models\Referral;
use WC_Affiliate\Models\Settings;
use WC_Affiliate\Models\Transaction;
use WC_Affiliate\Models\Visit;
use WP_User;

/**
 * Plugin Installer Class
 *
 * Handles plugin installation and database setup including creating tables,
 * setting up roles, scheduling events, and initializing default options.
 *
 * @since   1.0.0
 * @package WC_Affiliate\Bootstrap
 */
class Installer {

	/**
	 * Run installation routines.
	 */
	public static function install(): void {
		$installer = new self();

		$installer->add_options();

		if ( ! $installer->is_database_up_to_date() ) {
			$installer->create_tables();
			$installer->update_db_version();
		}

		// Migrate legacy settings if they exist.
		if ( ! get_option( 'wc_affiliate_settings_migrated' ) ) {
			$installer->migrate_legacy_settings();
		}

		// Schedule daily events.
		if ( ! wp_next_scheduled( 'wc_affiliate_daily' ) ) {
			wp_schedule_event( current_time( 'mysql' ), 'daily', 'wc_affiliate_daily' );
		}

		// Create affiliate role.
		$installer->create_affiliate_role();

		// Set default options.
		$installer->set_default_options();
	}

	/**
	 * Add plugin activation options.
	 *
	 * Sets activation flags and redirect transient for the setup wizard.
	 * The capability check is intentionally deferred to admin_init where
	 * the user context is fully initialised (current_user_can() is
	 * unreliable inside register_activation_hook).
	 *
	 * @return void
	 */
	protected function add_options(): void {
		// Set a flag that indicates the plugin has been activated.
		update_option( 'wc_affiliate_activated', true );

		// Set transient for setup wizard redirect.
		// Capability and wizard-state checks happen in Admin\Init::maybe_redirect_to_wizard().
		set_transient( 'wc_affiliate_activation_redirect', true, 60 );
	}

	/**
	 * Check if the database is up to date.
	 *
	 * @return bool
	 */
	protected function is_database_up_to_date(): bool {
		$installed_ver = get_option( 'wc_affiliate_db_version' );

		return version_compare( $installed_ver, WC_AFFILIATE_VERSION, '=' );
	}

	/**
	 * Create database tables.
	 */
	protected function create_tables(): void {
		// Create visits table.
		$visit_model    = new Visit();
		$visits_table   = new Database( $visit_model->get_table_name() );
		$visits_columns = array(
			'id'           => 'int(11) NOT NULL AUTO_INCREMENT',
			'affiliate'    => 'int(11) NOT NULL',
			'referral'     => 'int(11) NOT NULL DEFAULT 0',
			'page_url'     => 'varchar(255) NOT NULL',
			'referrer_url' => 'varchar(255) NOT NULL DEFAULT ""',
			'campaign'     => 'varchar(255) NOT NULL DEFAULT ""',
			'ip'           => 'varchar(15) NOT NULL',
			'time'         => 'int(10) NOT NULL',
		);
		$visits_options = array(
			'primary_key' => 'id',
			'indexes'     => array(
				'affiliate_index' => 'affiliate',
				'referral_index'  => 'referral',
				'time_index'      => 'time',
			),
		);
		$visits_table->create_table( $visits_columns, $visits_options );

		// Create referrals table.
		$referral_model    = new Referral();
		$referrals_table   = new Database( $referral_model->get_table_name() );
		$referrals_columns = array(
			'id'             => 'int(11) NOT NULL AUTO_INCREMENT',
			'affiliate'      => 'int(11) NOT NULL',
			'type'           => 'varchar(32) NOT NULL',
			'visit'          => 'int(11) NOT NULL DEFAULT 0',
			'order_id'       => 'int(16) NOT NULL',
			'products'       => 'text NOT NULL',
			'order_total'    => 'FLOAT(16,2) NOT NULL',
			'commission'     => 'FLOAT(16,2) NOT NULL',
			'payment_status' => 'varchar(16) NOT NULL',
			'transaction_id' => 'int(16) NOT NULL DEFAULT 0',
			'time'           => 'int(10) NOT NULL',
		);
		$referrals_options = array(
			'primary_key' => 'id',
			'indexes'     => array(
				'affiliate_index'      => 'affiliate',
				'visit_index'          => 'visit',
				'order_id_index'       => 'order_id',
				'payment_status_index' => 'payment_status',
				'transaction_id_index' => 'transaction_id',
				'time_index'           => 'time',
			),
		);
		$referrals_table->create_table( $referrals_columns, $referrals_options );

		// Create transactions table.
		$transaction_model    = new Transaction();
		$transactions_table   = new Database( $transaction_model->get_table_name() );
		$transactions_columns = array(
			'id'              => 'int(11) NOT NULL AUTO_INCREMENT',
			'affiliate'       => 'int(11) NOT NULL',
			'amount'          => 'FLOAT(16,2) NOT NULL',
			'payment_method'  => 'varchar(255) NOT NULL',
			'payment_details' => 'LONGTEXT DEFAULT NULL',
			'txn_id'          => 'varchar(255) NOT NULL',
			'status'          => 'varchar(16) NOT NULL',
			'notes'           => 'TEXT DEFAULT NULL',
			'request_at'      => 'int(10) NOT NULL',
			'process_at'      => 'int(10) NOT NULL',
		);
		$transactions_options = array(
			'primary_key' => 'id',
			'indexes'     => array(
				'affiliate_index'  => 'affiliate',
				'status_index'     => 'status',
				'request_at_index' => 'request_at',
				'process_at_index' => 'process_at',
			),
		);
		$transactions_table->create_table( $transactions_columns, $transactions_options );
	}

	/**
	 * Update or add the database version to the options table.
	 */
	protected function update_db_version(): void {
		update_option( 'wc_affiliate_db_version', WC_AFFILIATE_VERSION );
	}

	/**
	 * Create affiliate role
	 */
	protected function create_affiliate_role(): void {
		global $wp_roles;

		$author_role = $wp_roles->get_role( 'author' );

		if ( $author_role ) {
			// Clone the 'Author' role capabilities.
			$new_role_name = 'affiliate';

			// Check if the new role name doesn't already exist.
			if ( ! isset( $wp_roles->roles[ $new_role_name ] ) ) {
				$capabilities = $author_role->capabilities;

				// Add a new role based on the 'Author' role.
				add_role( $new_role_name, 'Affiliate', $capabilities );
			}
		}
	}

	/**
	 * Migrate legacy settings from old flat structure to new nested structure
	 */
	public function migrate_legacy_settings(): void {
		if ( get_option( 'wc_affiliate_settings_migrated' ) ) {
			return;
		}

		$option_model = new Settings();

		// Get legacy to new settings mapping from reusable function.
		$mappings = wca_get_legacy_settings_mappings();

		$settings_data = array();

		// Migrate each legacy option group.
		foreach ( $mappings as $legacy_option => $mapping ) {
			$legacy_data = get_option( $legacy_option, array() );
			if ( empty( $legacy_data ) ) {
				continue;
			}

			foreach ( $mapping as $legacy_key => $new_field_path ) {
				if ( ! isset( $legacy_data[ $legacy_key ] ) ) {
					continue;
				}

				$value = $legacy_data[ $legacy_key ];
				$value = apply_filters( 'wc_affiliate_legacy_option_value', $value, $legacy_key, $legacy_data, $new_field_path );

				wca_update_nested_value( $settings_data, $new_field_path, $value );
			}

			// Keep legacy options for backward compatibility during transition.
			// They can be removed in a future version.
		}

		if ( ! empty( $settings_data ) ) {
			// Update all settings data.
			$option_model->update_settings_data( $settings_data );
		}

		// Always mark migration as complete — even on fresh installs with no
		// legacy data — so that migrate_legacy_settings() does not run on
		// every init request unnecessarily.
		update_option( 'wc_affiliate_settings_migrated', '1' );
	}

	/**
	 * Iterates over all users that carry the '_wc_affiliate_status' meta key and
	 * assigns the 'affiliate' role to those whose status equals 'active'.
	 *
	 * A long-lived transient is used as a one-time execution guard so that this
	 * bulk operation does not repeat on every page load after the initial run.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function assign_affiliate_role_to_existing_users() {
		// Bail early if the bulk assignment has already been performed.
		if ( get_transient( 'affiliate_role_assigned' ) ) {
			return;
		}

		$users = get_users(
			array(
				'role__not_in' => array( 'affiliate' ),
				'meta_query'   => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
					array(
						'key'     => '_wc_affiliate_status',
						'compare' => 'EXISTS',
					),
				),
				'fields'       => 'all',
				'number'       => -1,
			)
		);

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

		foreach ( $users as $user ) {
			$affiliate_status = get_user_meta( $user->ID, '_wc_affiliate_status', true );

			// Only assign the role when the affiliate status is explicitly 'active'.
			if ( ! empty( $affiliate_status ) && 'active' === $affiliate_status ) {
				$user_obj = new WP_User( $user->ID );
				$user_obj->add_role( 'affiliate' );
			}
		}

		// Mark bulk assignment as complete for one year.
		set_transient( 'affiliate_role_assigned', true, DAY_IN_SECONDS * 365 );
	}

	/**
	 * Set default options using Settings model.
	 *
	 * Writes defaults to the single wc_affiliate_settings option only when it
	 * does not yet contain any data. Per-group writes (wc_affiliate_basic, etc.)
	 * belong to the legacy architecture and must not be used here — they would
	 * collide with the legacy-detection logic in wc_affiliate_has_legacy_options().
	 */
	protected function set_default_options(): void {
		$option_model = new Settings();

		if ( empty( $option_model->get_all_settings() ) ) {
			$defaults = $option_model->get_defaults();
			$option_model->update_settings_data( $defaults );
		}
	}
}
