<?php

namespace CelerSearch\CLI;

use CelerSearch\Factories\ServiceFactory;
use CelerSearch\Repositories\ServiceRepository;
use CelerSearch\Utilities\PortableConfig;
use WP_CLI;
use WP_CLI\Formatter;

/**
 * Manage CelerSearch services via WP-CLI.
 */
class ServiceCommand {

	/**
	 * List all CelerSearch services.
	 *
	 * ## OPTIONS
	 *
	 * [--format=<format>]
	 * : Output format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - csv
	 *   - json
	 *   - yaml
	 * ---
	 *
	 * ## EXAMPLES
	 *
	 *     # List all services
	 *     wp celersearch service list
	 *
	 *     # Output as JSON
	 *     wp celersearch service list --format=json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional arguments.
	 * @param array $assoc_args Associative arguments.
	 */
	public function list( $args, $assoc_args ) {
		$repo     = new ServiceRepository();
		$services = $repo->get( [], 1, 100 );

		if ( empty( $services ) ) {
			WP_CLI::warning( 'No services found.' );
			return;
		}

		$items = [];
		foreach ( $services as $service_config ) {
			$config = $service_config->getConfig();
			$url    = isset( $config->url ) ? $config->url : '-';

			// Mask API key for security.
			$api_key = isset( $config->api_key ) ? substr( $config->api_key, 0, 8 ) . '...' : '-';

			$items[] = [
				'ID'         => $service_config->getId(),
				'Name'       => $service_config->getName(),
				'Provider'   => $service_config->getProvider(),
				'URL'        => $url,
				'Status'     => $service_config->getStatus() ?: 'pending',
				'Checked At' => $service_config->getCheckedAt() ?: '-',
			];
		}

		$format    = WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
		$formatter = new Formatter( $assoc_args, [ 'ID', 'Name', 'Provider', 'URL', 'Status', 'Checked At' ] );
		$formatter->display_items( $items );
	}

	/**
	 * Test connection to a service.
	 *
	 * ## OPTIONS
	 *
	 * <service>
	 * : Service ID.
	 *
	 * [--verbose]
	 * : Show detailed response information.
	 *
	 * ## EXAMPLES
	 *
	 *     # Test service connection
	 *     wp celersearch service test 1
	 *
	 *     # Test with verbose output
	 *     wp celersearch service test 1 --verbose
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional arguments.
	 * @param array $assoc_args Associative arguments.
	 */
	public function test( $args, $assoc_args ) {
		$service_id = (int) $args[0];
		$verbose    = WP_CLI\Utils\get_flag_value( $assoc_args, 'verbose', false );

		$repo           = new ServiceRepository();
		$service_config = $repo->find( $service_id );

		if ( ! $service_config ) {
			WP_CLI::error( "Service not found: {$service_id}" );
		}

		$service_name = $service_config->getName();
		$provider     = $service_config->getProvider();
		$config       = $service_config->getConfig();

		WP_CLI::log( sprintf( 'Testing connection to "%s" (%s)...', $service_name, $provider ) );

		if ( $verbose ) {
			$url = isset( $config->url ) ? $config->url : 'Not configured';
			WP_CLI::log( sprintf( '  URL: %s', $url ) );
		}

		$start_time = microtime( true );

		try {
			$service = ServiceFactory::create( $service_id );

			// Test by getting health/version info based on provider.
			$success = $this->test_service_connection( $service, $provider, $verbose );
			$duration = round( ( microtime( true ) - $start_time ) * 1000, 2 );

			if ( $success ) {
				// Update service status to connected.
				$repo->update( $service_id, [
					'status'     => ServiceRepository::STATUS_CONNECTED,
					'checked_at' => gmdate( 'Y-m-d H:i:s' ),
				] );

				WP_CLI::success( sprintf( 'Connection successful! (%sms)', $duration ) );
			} else {
				WP_CLI::error( 'Connection test failed.' );
			}
		} catch ( \Exception $e ) {
			$duration = round( ( microtime( true ) - $start_time ) * 1000, 2 );

			// Update service status to failed.
			$repo->update( $service_id, [
				'status'     => ServiceRepository::STATUS_FAILING,
				'checked_at' => gmdate( 'Y-m-d H:i:s' ),
			] );

			WP_CLI::error( sprintf( 'Connection failed (%sms): %s', $duration, $e->getMessage() ) );
		}
	}

	/**
	 * Test service connection based on provider type.
	 *
	 * @param object $service  The service instance.
	 * @param string $provider The provider type.
	 * @param bool   $verbose  Whether to show verbose output.
	 *
	 * @return bool
	 */
	private function test_service_connection( $service, $provider, $verbose ) {
		switch ( $provider ) {
			case 'meilisearch':
				return $this->test_meilisearch_connection( $service, $verbose );

			default:
				WP_CLI::warning( "Unknown provider: {$provider}. Attempting generic test..." );
				return $this->test_generic_connection( $service, $verbose );
		}
	}

	/**
	 * Test MeiliSearch connection.
	 *
	 * @param object $service The MeiliSearch service instance.
	 * @param bool   $verbose Whether to show verbose output.
	 *
	 * @return bool
	 */
	private function test_meilisearch_connection( $service, $verbose ) {
		// Use reflection to access the protected client.
		$reflection = new \ReflectionClass( $service );
		$property   = $reflection->getProperty( 'client' );
		$property->setAccessible( true );
		$client = $property->getValue( $service );

		// Get health status.
		$health = $client->health();

		if ( $verbose ) {
			WP_CLI::log( sprintf( '  Health Status: %s', $health['status'] ?? 'unknown' ) );
		}

		// Get version info.
		$version = $client->version();

		if ( $verbose ) {
			WP_CLI::log( sprintf( '  MeiliSearch Version: %s', $version['pkgVersion'] ?? 'unknown' ) );
			WP_CLI::log( sprintf( '  Commit SHA: %s', substr( $version['commitSha'] ?? 'unknown', 0, 8 ) ) );
			WP_CLI::log( sprintf( '  Commit Date: %s', $version['commitDate'] ?? 'unknown' ) );
		}

		return isset( $health['status'] ) && $health['status'] === 'available';
	}

	/**
	 * Test generic connection (fallback).
	 *
	 * @param object $service The service instance.
	 * @param bool   $verbose Whether to show verbose output.
	 *
	 * @return bool
	 */
	private function test_generic_connection( $service, $verbose ) {
		// For unknown providers, just check if the service was created successfully.
		// The constructor should throw if connection fails.
		return true;
	}

	/**
	 * Export a service as a portable JSON envelope. API keys are redacted
	 * unless --include-secrets is passed.
	 *
	 * ## OPTIONS
	 *
	 * <service>
	 * : Service ID or name.
	 *
	 * [--include-secrets]
	 * : Embed credentials (API key, etc.) in the export. Off by default.
	 *
	 * [--output=<path>]
	 * : Write JSON to this file instead of stdout.
	 *
	 * ## EXAMPLES
	 *
	 *     # Print envelope (secrets redacted) to stdout
	 *     wp celersearch service export 1
	 *
	 *     # Include API key and write to file
	 *     wp celersearch service export LocalMeili --include-secrets --output=/tmp/svc.json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional arguments.
	 * @param array $assoc_args Associative arguments.
	 */
	public function export( $args, $assoc_args ) {
		$identifier      = $args[0];
		$include_secrets = (bool) WP_CLI\Utils\get_flag_value( $assoc_args, 'include-secrets', false );

		$service_config = $this->resolve_service( $identifier );
		if ( ! $service_config ) {
			WP_CLI::error( "Service not found: {$identifier}" );
		}

		try {
			$envelope = PortableConfig::export_service( $service_config, $include_secrets );
		} catch ( \Throwable $e ) {
			WP_CLI::error( 'Export failed: ' . $e->getMessage() );
		}

		$json   = wp_json_encode( $envelope, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
		$output = WP_CLI\Utils\get_flag_value( $assoc_args, 'output', null );
		if ( $output ) {
			$bytes = file_put_contents( $output, $json );
			if ( $bytes === false ) {
				WP_CLI::error( "Could not write to {$output}" );
			}
			WP_CLI::success( sprintf( 'Wrote %d bytes to %s', $bytes, $output ) );
			if ( ! $include_secrets ) {
				WP_CLI::log( 'Note: API key redacted. Pass --include-secrets to embed credentials.' );
			}
		} else {
			WP_CLI::log( $json );
		}
	}

	/**
	 * Import a service from a portable JSON envelope. Always creates a new
	 * record — name collisions get a " (1)", " (2)", … suffix.
	 *
	 * ## OPTIONS
	 *
	 * <file>
	 * : Path to a CelerSearch service export file.
	 *
	 * ## EXAMPLES
	 *
	 *     wp celersearch service import /tmp/svc.json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional arguments.
	 * @param array $assoc_args Associative arguments.
	 */
	public function import( $args, $assoc_args ) {
		$envelope = $this->read_envelope_file( $args[0] );

		try {
			$record = PortableConfig::import_service( $envelope );
		} catch ( \InvalidArgumentException $e ) {
			WP_CLI::error( $e->getMessage() );
		} catch ( \Throwable $e ) {
			WP_CLI::error( 'Import failed: ' . $e->getMessage() );
		}

		WP_CLI::success( sprintf(
			'Imported service #%d (%s, %s).',
			$record->getId(),
			$record->getName(),
			$record->getProvider()
		) );

		if ( empty( $envelope['secrets_included'] ) ) {
			WP_CLI::warning( 'Credentials were not included in the export — edit the service to add an API key before use.' );
		}
	}

	/**
	 * Resolve service by ID or name.
	 *
	 * @param string $identifier Service ID or name.
	 *
	 * @return \CelerSearch\DataTransfer\ServiceConfig|null
	 */
	private function resolve_service( $identifier ) {
		$repo = new ServiceRepository();
		if ( is_numeric( $identifier ) ) {
			$service = $repo->find( (int) $identifier );
			if ( $service ) {
				return $service;
			}
		}
		try {
			$matches = $repo->get( [ 'name' => $identifier ], 1, 1 );
		} catch ( \Exception $e ) {
			return null;
		}
		return $matches ? $matches[0] : null;
	}

	/**
	 * Read and decode a JSON envelope file.
	 *
	 * @param string $path File path.
	 *
	 * @return array
	 */
	private function read_envelope_file( $path ) {
		if ( ! is_readable( $path ) ) {
			WP_CLI::error( "Cannot read file: {$path}" );
		}
		$contents = file_get_contents( $path );
		if ( $contents === false || $contents === '' ) {
			WP_CLI::error( "File is empty: {$path}" );
		}
		$envelope = json_decode( $contents, true );
		if ( ! is_array( $envelope ) ) {
			WP_CLI::error( "File is not valid JSON: {$path}" );
		}
		return $envelope;
	}
}
