<?php

namespace RadiusTheme\RadiusBooking\ExportImport;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use RadiusTheme\RadiusBooking\Common\Keys;
use RadiusTheme\RadiusBooking\ExportImport\Contracts\ExporterInterface;
use RadiusTheme\RadiusBooking\ExportImport\Contracts\ImporterInterface;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\SettingsExporter;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\CategoryExporter;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\LocationExporter;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\ServiceExporter;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\StaffExporter;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\CustomerExporter;
use RadiusTheme\RadiusBooking\ExportImport\Exporters\AppointmentExporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\SettingsImporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\CategoryImporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\LocationImporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\ServiceImporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\StaffImporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\CustomerImporter;
use RadiusTheme\RadiusBooking\ExportImport\Importers\AppointmentImporter;

/**
 * Class ExportImportService
 *
 * Orchestrates export/import operations across all modules.
 *
 * @package RadiusTheme\RadiusBooking\ExportImport
 */
class ExportImportService {

	/**
	 * Registered exporters keyed by module name.
	 *
	 * @var array<string, ExporterInterface>
	 */
	private array $exporters = array();

	/**
	 * Registered importers keyed by module name.
	 *
	 * @var array<string, ImporterInterface>
	 */
	private array $importers = array();

	/**
	 * Constructor — registers all built-in modules.
	 */
	public function __construct() {
		$this->registerModules();
	}

	/**
	 * Register all built-in exporter and importer modules.
	 * Pro modules can be added via the 'rtrb_export_import_exporters' and
	 * 'rtrb_export_import_importers' filters.
	 */
	private function registerModules(): void {
		$exporters = array(
			new SettingsExporter(),
			new CategoryExporter(),
			new LocationExporter(),
			new ServiceExporter(),
			new StaffExporter(),
			new CustomerExporter(),
			new AppointmentExporter(),
		);

		$importers = array(
			new SettingsImporter(),
			new CategoryImporter(),
			new LocationImporter(),
			new ServiceImporter(),
			new StaffImporter(),
			new CustomerImporter(),
			new AppointmentImporter(),
		);

		/**
		 * Filter to register additional exporters (e.g. from pro plugin).
		 *
		 * @param ExporterInterface[] $exporters Array of exporter instances.
		 */
		$exporters = apply_filters( 'rtrb_export_import_exporters', $exporters );

		/**
		 * Filter to register additional importers (e.g. from pro plugin).
		 *
		 * @param ImporterInterface[] $importers Array of importer instances.
		 */
		$importers = apply_filters( 'rtrb_export_import_importers', $importers );

		foreach ( $exporters as $exporter ) {
			$this->exporters[ $exporter->getModuleName() ] = $exporter;
		}

		foreach ( $importers as $importer ) {
			$this->importers[ $importer->getModuleName() ] = $importer;
		}
	}

	/**
	 * Get available modules with their record counts.
	 *
	 * @return array [ [ 'name' => string, 'label' => string, 'count' => int ] ]
	 */
	public function getAvailableModules(): array {
		$modules = array();

		$labels = apply_filters(
			'rtrb_export_import_module_labels',
			array(
				'settings'        => __( 'Settings', 'radius-booking' ),
				'category'        => __( 'Categories', 'radius-booking' ),
				'location'        => __( 'Locations', 'radius-booking' ),
				'service'         => __( 'Services', 'radius-booking' ),
				'staff'           => __( 'Staff', 'radius-booking' ),
				'customer'        => __( 'Customers', 'radius-booking' ),
				'appointment'     => __( 'Appointments', 'radius-booking' ),
				'coupon'          => __( 'Coupons', 'radius-booking' ),
				'custom_field'    => __( 'Custom Fields', 'radius-booking' ),
				'dynamic_pricing' => __( 'Dynamic Pricing', 'radius-booking' ),
				'payout'          => __( 'Payouts', 'radius-booking' ),
			)
		);

		foreach ( $this->exporters as $name => $exporter ) {
			$modules[] = array(
				'name'         => $name,
				'label'        => $labels[ $name ] ?? $name,
				'count'        => $exporter->count(),
				'dependencies' => $exporter->getDependencies(),
			);
		}

		return $modules;
	}

	/**
	 * Get the exporter for a given module.
	 *
	 * @param string $module Module name.
	 *
	 * @return ExporterInterface|null
	 */
	public function getExporter( string $module ): ?ExporterInterface {
		return $this->exporters[ $module ] ?? null;
	}

	/**
	 * Get the importer for a given module.
	 *
	 * @param string $module Module name.
	 *
	 * @return ImporterInterface|null
	 */
	public function getImporter( string $module ): ?ImporterInterface {
		return $this->importers[ $module ] ?? null;
	}

	/**
	 * Resolve modules in dependency order via topological sort.
	 *
	 * @param array $modules Array of module names.
	 *
	 * @return array Sorted module names.
	 */
	public function resolveDependencyOrder( array $modules ): array {
		$resolved = array();
		$visited  = array();

		foreach ( $modules as $module ) {
			$this->resolveModule( $module, $modules, $resolved, $visited );
		}

		return $resolved;
	}

	/**
	 * Recursive helper for topological sort.
	 *
	 * @param string $module   Current module.
	 * @param array  $selected Selected modules.
	 * @param array  $resolved Already resolved modules.
	 * @param array  $visited  Visit tracking for cycle detection.
	 */
	private function resolveModule( string $module, array $selected, array &$resolved, array &$visited ): void {
		if ( in_array( $module, $resolved, true ) ) {
			return;
		}
		if ( in_array( $module, $visited, true ) ) {
			return; // Cycle detected, skip.
		}

		$visited[] = $module;

		$exporter = $this->exporters[ $module ] ?? null;
		if ( $exporter ) {
			foreach ( $exporter->getDependencies() as $dep ) {
				if ( in_array( $dep, $selected, true ) ) {
					$this->resolveModule( $dep, $selected, $resolved, $visited );
				}
			}
		}

		$resolved[] = $module;
	}

	/**
	 * Get export counts for selected modules.
	 *
	 * @param array $modules Module names.
	 *
	 * @return array [ module_name => count ]
	 */
	public function getExportCounts( array $modules ): array {
		$counts = array();
		foreach ( $modules as $module ) {
			$exporter = $this->exporters[ $module ] ?? null;
			if ( $exporter ) {
				$counts[ $module ] = $exporter->count();
			}
		}
		return $counts;
	}

	/**
	 * Export a single batch for a module.
	 *
	 * @param string $module Module name.
	 * @param int    $offset Batch offset.
	 * @param int    $limit  Batch size.
	 *
	 * @return array Exported records.
	 */
	public function exportBatch( string $module, int $offset = 0, int $limit = 500 ): array {
		$exporter = $this->exporters[ $module ] ?? null;
		if ( ! $exporter ) {
			return array();
		}
		return $exporter->export( $offset, $limit );
	}

	/**
	 * Import a single batch for a module.
	 *
	 * @param string   $module      Module name.
	 * @param array    $records     Records to import.
	 * @param IdMapper $id_mapper   ID mapper instance.
	 *
	 * @return int Number of records imported.
	 */
	public function importBatch( string $module, array $records, IdMapper $id_mapper ): int {
		$importer = $this->importers[ $module ] ?? null;
		if ( ! $importer ) {
			return 0;
		}
		return $importer->import( $records, $id_mapper );
	}

	/**
	 * Validate import file structure.
	 *
	 * @param array $data Parsed JSON data.
	 *
	 * @return array Validation result with 'valid' (bool) and 'errors' (array).
	 */
	public function validateImportFile( array $data ): array {
		$errors = array();

		if ( ! isset( $data['metadata'] ) ) {
			$errors[] = __( 'Missing metadata section.', 'radius-booking' );
		}

		if ( ! isset( $data['data'] ) || ! is_array( $data['data'] ) ) {
			$errors[] = __( 'Missing or invalid data section.', 'radius-booking' );
		}

		if ( isset( $data['metadata']['plugin'] ) && 'radius-booking' !== $data['metadata']['plugin'] ) {
			$errors[] = __( 'Export file is not from Radius Booking plugin.', 'radius-booking' );
		}

		if ( empty( $errors ) && isset( $data['metadata']['modules'] ) ) {
			foreach ( $data['metadata']['modules'] as $module ) {
				if ( ! isset( $this->importers[ $module ] ) ) {
					$errors[] = sprintf(
						/* translators: %s: module name */
						__( 'Unknown module: %s', 'radius-booking' ),
						$module
					);
				}
			}
		}

		return array(
			'valid'  => empty( $errors ),
			'errors' => $errors,
		);
	}

	/**
	 * Get the import plan from a validated file.
	 *
	 * @param array $data Parsed JSON data.
	 *
	 * @return array Import plan with ordered modules and counts.
	 */
	public function getImportPlan( array $data ): array {
		$modules = $data['metadata']['modules'] ?? array();
		$ordered = $this->resolveDependencyOrder( $modules );

		$plan = array();
		foreach ( $ordered as $module ) {
			$count  = 0;
			if ( isset( $data['data'][ $module ] ) ) {
				$count = is_array( $data['data'][ $module ] ) ? count( $data['data'][ $module ] ) : 1;
			}
			$plan[] = array(
				'module' => $module,
				'count'  => $count,
			);
		}

		return $plan;
	}

	/**
	 * Build export metadata header.
	 *
	 * @param array $modules  Selected module names.
	 * @param array $counts   Module counts.
	 *
	 * @return array Metadata array.
	 */
	public function buildExportMetadata( array $modules, array $counts ): array {
		return array(
			'plugin'      => 'radius-booking',
			'version'     => defined( 'RADIUS_BOOKING_VERSION' ) ? RADIUS_BOOKING_VERSION : '1.0.0',
			'exported_at' => current_time( 'c' ),
			'site_url'    => get_site_url(),
			'modules'     => $modules,
			'counts'      => $counts,
		);
	}

	/**
	 * Export all records for a single module (no pagination).
	 * Used by the developer facade API.
	 *
	 * @param string $module Module name.
	 *
	 * @return array All records for the module.
	 */
	public function exportAll( string $module ): array {
		$exporter = $this->exporters[ $module ] ?? null;
		if ( ! $exporter ) {
			return array();
		}

		$all_records = array();
		$offset      = 0;
		$limit       = 500;

		do {
			$batch = $exporter->export( $offset, $limit );
			$all_records = array_merge( $all_records, $batch );
			$offset += $limit;
		} while ( count( $batch ) >= $limit );

		return $all_records;
	}

	/**
	 * Export multiple modules into a full export structure.
	 * Used by the developer facade API.
	 *
	 * @param array $modules Module names.
	 *
	 * @return array Full export structure with metadata and data.
	 */
	public function exportModules( array $modules ): array {
		$ordered = $this->resolveDependencyOrder( $modules );
		$data    = array();
		$counts  = array();

		foreach ( $ordered as $module ) {
			$records          = $this->exportAll( $module );
			$data[ $module ]  = $records;
			$counts[ $module ] = count( $records );
		}

		return array(
			'metadata' => $this->buildExportMetadata( $ordered, $counts ),
			'data'     => $data,
		);
	}

	/**
	 * Import from a full export structure.
	 * Used by the developer facade API.
	 *
	 * @param array $export_data Full export structure.
	 *
	 * @return array Import results per module.
	 */
	public function importFromData( array $export_data ): array {
		$validation = $this->validateImportFile( $export_data );
		if ( ! $validation['valid'] ) {
			return array(
				'success' => false,
				'errors'  => $validation['errors'],
			);
		}

		$plan      = $this->getImportPlan( $export_data );
		$id_mapper = new IdMapper( IdMapper::generateSessionKey() );
		$results   = array();

		foreach ( $plan as $step ) {
			$module  = $step['module'];
			$records = $export_data['data'][ $module ] ?? array();

			if ( 'settings' === $module ) {
				$imported = $this->importBatch( $module, $records, $id_mapper );
			} else {
				$imported = 0;
				$chunks   = array_chunk( $records, 500 );
				foreach ( $chunks as $chunk ) {
					$imported += $this->importBatch( $module, $chunk, $id_mapper );
					$id_mapper->save();
				}
			}

			$results[ $module ] = $imported;
		}

		$id_mapper->cleanup();

		update_option( Keys::RADIUS_BOOKING_SETUP_COMPLETED, true );

		return array(
			'success' => true,
			'results' => $results,
		);
	}
}
