<?php

namespace SugarCalendarBookings\Migration\Sources\Amelia;

use SugarCalendarBookings\Migration\SourceAdapterInterface;
use SugarCalendarBookings\Migration\Sources\Amelia\Mappers\AppointmentMapper;
use SugarCalendarBookings\Migration\Sources\Amelia\Mappers\CustomerMapper;
use SugarCalendarBookings\Migration\Sources\Amelia\Mappers\LocationMapper;
use SugarCalendarBookings\Migration\Sources\Amelia\Mappers\ServiceMapper;
use SugarCalendarBookings\Plugin;
use WP_Error;

/**
 * Amelia Adapter.
 *
 * Adapter for migrating from Amelia plugin (Lite and Pro).
 * Uses Plugin container (Loader pattern) for mapper dependency injection.
 * Pro-only mappers are registered in Pro\Loader and auto-detected.
 *
 * @since 1.3.0
 */
class AmeliaAdapter implements SourceAdapterInterface {

	/**
	 * Whether Amelia Pro is detected.
	 *
	 * @since 1.3.0
	 *
	 * @var bool
	 */
	private $is_pro;

	/**
	 * Mappers.
	 *
	 * @since 1.3.0
	 *
	 * @var array<string, object>
	 */
	protected $mappers;

	/**
	 * Constructor.
	 *
	 * @since 1.3.0
	 */
	public function __construct() {

		$this->is_pro = self::is_amelia_pro();

		$this->init_mappers();
	}

	/**
	 * Detect if Amelia Pro is installed.
	 *
	 * Checks if the Pro license file exists in Amelia plugin.
	 *
	 * @since 1.3.0
	 *
	 * @return bool
	 */
	public static function is_amelia_pro(): bool {

		$amelia_path = self::get_amelia_plugin_path();

		if ( ! $amelia_path ) {
			return false;
		}

		// Check if Pro license file exists.
		$pro_licence_file = $amelia_path . '/src/Infrastructure/Licence/Pro/Licence.php';

		return file_exists( $pro_licence_file );
	}

	/**
	 * Get the path to the Amelia plugin.
	 *
	 * Checks active plugins first, then falls back to any installed Amelia plugin.
	 *
	 * @since 1.3.0
	 *
	 * @return string|null Plugin path or null if not found.
	 */
	private static function get_amelia_plugin_path(): ?string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		// First, check active plugins option for Amelia.
		$active_plugins = get_option( 'active_plugins', [] );

		foreach ( $active_plugins as $plugin ) {
			// Amelia plugin files are named ameliabooking.php or similar.
			if ( strpos( $plugin, 'ameliabooking' ) !== false ) {
				$plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin );

				if ( is_dir( $plugin_dir ) ) {
					return $plugin_dir;
				}
			}
		}

		// Fallback: scan for any Amelia plugin folder.
		$plugin_dirs = glob( WP_PLUGIN_DIR . '/ameliabooking*', GLOB_ONLYDIR );

		if ( ! empty( $plugin_dirs ) ) {
			// Prefer 'ameliabooking' (standard name) if it exists.
			foreach ( $plugin_dirs as $dir ) {
				if ( basename( $dir ) === 'ameliabooking' ) {
					return $dir;
				}
			}

			// Otherwise, return the first one found.
			return $plugin_dirs[0];
		}

		return null;
	}

	/**
	 * Initialize mappers from Plugin container (Loader pattern).
	 *
	 * Base mappers are registered in Loader.php.
	 * Pro adds EmployeeMapper via Pro\Loader.php.
	 *
	 * @since 1.3.0
	 */
	protected function init_mappers() {

		// Base mappers (no employee - SCB Lite uses default employee).
		$this->mappers = [
			'service'     => Plugin::get( ServiceMapper::class ),
			'customer'    => Plugin::get( CustomerMapper::class ),
			'location'    => Plugin::get( LocationMapper::class ),
			'appointment' => Plugin::get( AppointmentMapper::class ),
		];
	}

	/**
	 * Check if Pro version is detected.
	 *
	 * @since 1.3.0
	 *
	 * @return bool
	 */
	public function is_pro(): bool {

		return $this->is_pro;
	}

	/**
	 * Get supported entity types in correct import order.
	 *
	 * CRITICAL: Order matters due to entity dependencies!
	 * - location, customer: No dependencies (first)
	 * - employee: No dependencies (included for Amelia Pro even without mapper for skip tracking)
	 * - service: Requires locations, employees
	 * - appointment: Requires services, customers, employees, locations
	 *
	 * @since 1.3.0
	 *
	 * @return array<string>
	 */
	public function get_supported_entities(): array {

		// Return in dependency order.
		// Include employee for Amelia Pro even without mapper - will be skipped but tracked.
		if ( $this->is_pro ) {
			return [ 'location', 'customer', 'employee', 'service', 'appointment' ];
		}

		return [ 'location', 'customer', 'service', 'appointment' ];
	}

	/**
	 * Fetch data from Amelia tables with pagination.
	 *
	 * @since 1.3.0
	 *
	 * @param string               $entity_type Entity type.
	 * @param array<string, mixed> $options     Options: limit, offset.
	 *
	 * @return array<object>|WP_Error
	 */
	public function fetch( string $entity_type, array $options = [] ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded

		global $wpdb;

		$limit  = isset( $options['limit'] ) ? (int) $options['limit'] : 100;
		$offset = isset( $options['offset'] ) ? (int) $options['offset'] : 0;

		switch ( $entity_type ) {
			case 'service':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				return $wpdb->get_results(
					$wpdb->prepare(
						"SELECT * FROM {$wpdb->prefix}amelia_services ORDER BY id ASC LIMIT %d OFFSET %d",
						$limit,
						$offset
					)
				) ?: [];

			case 'customer':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				return $wpdb->get_results(
					$wpdb->prepare(
						"SELECT * FROM {$wpdb->prefix}amelia_users WHERE type = 'customer' ORDER BY id ASC LIMIT %d OFFSET %d",
						$limit,
						$offset
					)
				) ?: [];

			case 'employee':
				// Pro only - providers/employees.
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				return $wpdb->get_results(
					$wpdb->prepare(
						"SELECT * FROM {$wpdb->prefix}amelia_users WHERE type = 'provider' ORDER BY id ASC LIMIT %d OFFSET %d",
						$limit,
						$offset
					)
				) ?: [];

			case 'location':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				return $wpdb->get_results(
					$wpdb->prepare(
						"SELECT * FROM {$wpdb->prefix}amelia_locations ORDER BY id ASC LIMIT %d OFFSET %d",
						$limit,
						$offset
					)
				) ?: [];

			case 'appointment':
				return $this->fetch_appointments( $limit, $offset ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement

			default:
				return new WP_Error( 'invalid_entity', 'Invalid entity type.' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
		}
	}

	/**
	 * Get total count of entities.
	 *
	 * @since 1.3.0
	 *
	 * @param string $entity_type Entity type.
	 *
	 * @return int|WP_Error
	 */
	public function count( string $entity_type ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		global $wpdb;

		switch ( $entity_type ) {
			case 'service':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}amelia_services" );
				break;

			case 'customer':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}amelia_users WHERE type = 'customer'" );
				break;

			case 'employee':
				// Pro only - providers/employees.
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}amelia_users WHERE type = 'provider'" );
				break;

			case 'location':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}amelia_locations" );
				break;

			case 'appointment':
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}amelia_customer_bookings WHERE appointmentId IS NOT NULL" );
				break;

			default:
				return new WP_Error( 'invalid_entity', 'Invalid entity type.' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
		}

		return $count !== null ? (int) $count : 0;
	}

	/**
	 * Fetch appointments with joined data and pagination.
	 *
	 * @since 1.3.0
	 *
	 * @param int $limit  Limit.
	 * @param int $offset Offset.
	 *
	 * @return array<object>
	 */
	private function fetch_appointments( int $limit, int $offset ) {

		global $wpdb;

		$query = $wpdb->prepare(
			"SELECT cb.*, a.serviceId, a.providerId, a.locationId, a.bookingStart, a.bookingEnd,
				a.internalNotes,
				p.id as paymentId, p.amount as paymentAmount, p.status as paymentStatus,
				p.gateway as paymentGateway
			FROM {$wpdb->prefix}amelia_customer_bookings cb
			LEFT JOIN {$wpdb->prefix}amelia_appointments a ON cb.appointmentId = a.id
			LEFT JOIN {$wpdb->prefix}amelia_payments p ON p.customerBookingId = cb.id
			WHERE cb.appointmentId IS NOT NULL
			ORDER BY cb.id ASC
			LIMIT %d OFFSET %d",
			$limit,
			$offset
		);

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
		return $wpdb->get_results( $query ) ?: [];
	}

	/**
	 * Map data by delegating to appropriate mapper.
	 *
	 * @since 1.3.0
	 *
	 * @param string               $entity_type Entity type.
	 * @param object               $raw_data    Raw data.
	 * @param array<string, mixed> $context     Context.
	 *
	 * @return array<string, mixed>|null
	 */
	public function map( string $entity_type, object $raw_data, array $context = [] ) {

		if ( ! isset( $this->mappers[ $entity_type ] ) ) {
			return null;
		}

		return $this->mappers[ $entity_type ]->map( $raw_data, $context );
	}

	/**
	 * Validate Amelia is installed.
	 *
	 * @since 1.3.0
	 *
	 * @return true|WP_Error
	 */
	public function validate() {

		global $wpdb;

		$table = $wpdb->prefix . 'amelia_services';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );

		if ( $exists !== $table ) {
			return new WP_Error( 'amelia_not_found', 'Amelia tables not found.' );
		}

		return true;
	}

	/**
	 * Get source ID.
	 *
	 * @since 1.3.0
	 *
	 * @return string
	 */
	public function get_id(): string {

		return 'amelia';
	}

	/**
	 * Get source name.
	 *
	 * @since 1.3.0
	 *
	 * @return string
	 */
	public function get_name(): string {

		return $this->is_pro ? 'Amelia Pro' : 'Amelia Lite';
	}
}
