<?php

namespace AtlasAiDev\Awfah\Abilities;

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

class Debug_Log_Abilities {

	/**
	 * Maximum number of lines to read from the debug log.
	 */
	const MAX_LINES = 1000;

	public static function get_abilities(): array {
		return [
			[
				'id'            => 'awfah-debug/get-debug-status',
				'module'        => 'error_debugger',
				'label'         => __( 'Get Debug Status', 'ai-workflow-automation-ai-agent-hub' ),
				'description'   => __( 'Check WordPress debug configuration and log file status.', 'ai-workflow-automation-ai-agent-hub' ),
				'category'      => 'awfah-debug',
				'input_schema'  => [
					'type'       => 'object',
					'properties' => (object) [],
				],
				'output_schema' => [
					'type'       => 'object',
					'properties' => [
						'wp_debug'     => [ 'type' => 'boolean', 'description' => __( 'Whether WP_DEBUG is enabled.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'wp_debug_log' => [ 'type' => 'string', 'description' => __( 'WP_DEBUG_LOG value (boolean or custom path).', 'ai-workflow-automation-ai-agent-hub' ) ],
						'log_path'     => [ 'type' => 'string', 'description' => __( 'Absolute path to the debug log file.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'log_exists'   => [ 'type' => 'boolean', 'description' => __( 'Whether the log file exists.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'log_size'     => [ 'type' => 'integer', 'description' => __( 'Log file size in bytes.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'log_modified' => [ 'type' => 'string', 'description' => __( 'Last modified datetime of the log file.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'writable'     => [ 'type' => 'boolean', 'description' => __( 'Whether the log file or its directory is writable.', 'ai-workflow-automation-ai-agent-hub' ) ],
					],
				],
				'meta'          => [
					'show_in_rest' => true,
					'mcp'          => [ 'public' => true ],
					'uri'          => 'wordpress://ai-agent-hub/debug/status',
					'mimeType'     => 'application/json',
				],
			],
			[
				'id'            => 'awfah-debug/read-debug-log',
				'module'        => 'error_debugger',
				'label'         => __( 'Read Debug Log', 'ai-workflow-automation-ai-agent-hub' ),
				'description'   => __( 'Read and parse recent error entries from the WordPress debug log. Filters to plugin and theme errors only.', 'ai-workflow-automation-ai-agent-hub' ),
				'category'      => 'awfah-debug',
				'input_schema'  => [
					'type'       => 'object',
					'properties' => [
						'lines'         => [
							'type'        => 'integer',
							'description' => __( 'Number of lines to read from the end of the log (default 100, max 1000).', 'ai-workflow-automation-ai-agent-hub' ),
						],
						'severity'      => [
							'type'        => 'string',
							'description' => __( 'Filter by severity: all, fatal, warning, notice, deprecated (default all).', 'ai-workflow-automation-ai-agent-hub' ),
							'enum'        => [ 'all', 'fatal', 'warning', 'notice', 'deprecated' ],
						],
						'source_filter' => [
							'type'        => 'string',
							'description' => __( 'Filter by source: all, plugins, themes (default all). Core errors are always excluded.', 'ai-workflow-automation-ai-agent-hub' ),
							'enum'        => [ 'all', 'plugins', 'themes' ],
						],
					],
				],
				'output_schema' => [
					'type'       => 'object',
					'properties' => [
						'entries' => [
							'type'        => 'array',
							'description' => __( 'Parsed log entries.', 'ai-workflow-automation-ai-agent-hub' ),
						],
						'total'   => [
							'type'        => 'integer',
							'description' => __( 'Total number of entries returned.', 'ai-workflow-automation-ai-agent-hub' ),
						],
					],
				],
				'meta'          => [
					'show_in_rest' => true,
					'mcp'          => [ 'public' => true ],
					'uri'          => 'wordpress://ai-agent-hub/debug/log',
					'mimeType'     => 'application/json',
				],
			],
			[
				'id'            => 'awfah-debug/get-error-source',
				'module'        => 'error_debugger',
				'label'         => __( 'Get Error Source Code', 'ai-workflow-automation-ai-agent-hub' ),
				'description'   => __( 'Read the source code around a specific error location. Restricted to plugin and theme files only.', 'ai-workflow-automation-ai-agent-hub' ),
				'category'      => 'awfah-debug',
				'input_schema'  => [
					'type'       => 'object',
					'required'   => [ 'file_path', 'line_number' ],
					'properties' => [
						'file_path'     => [
							'type'        => 'string',
							'description' => __( 'Absolute path to the source file (must be within wp-content/plugins/ or wp-content/themes/).', 'ai-workflow-automation-ai-agent-hub' ),
						],
						'line_number'   => [
							'type'        => 'integer',
							'description' => __( 'Line number where the error occurred.', 'ai-workflow-automation-ai-agent-hub' ),
						],
						'context_lines' => [
							'type'        => 'integer',
							'description' => __( 'Number of lines to show before and after the error line (default 10).', 'ai-workflow-automation-ai-agent-hub' ),
						],
					],
				],
				'output_schema' => [
					'type'       => 'object',
					'properties' => [
						'file_path'   => [ 'type' => 'string' ],
						'line_number' => [ 'type' => 'integer' ],
						'source_code' => [ 'type' => 'string', 'description' => __( 'Source code around the error line with line numbers.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'source_type' => [ 'type' => 'string', 'description' => __( 'plugin or theme.', 'ai-workflow-automation-ai-agent-hub' ) ],
						'source_name' => [ 'type' => 'string', 'description' => __( 'Plugin slug or theme name.', 'ai-workflow-automation-ai-agent-hub' ) ],
					],
				],
				'meta'          => [
					'show_in_rest' => true,
					'mcp'          => [ 'public' => true ],
				],
			],
			[
				'id'            => 'awfah-debug/clear-debug-log',
				'module'        => 'error_debugger',
				'label'         => __( 'Clear Debug Log', 'ai-workflow-automation-ai-agent-hub' ),
				'description'   => __( 'Truncate the WordPress debug log file.', 'ai-workflow-automation-ai-agent-hub' ),
				'category'      => 'awfah-debug',
				'input_schema'  => [
					'type'       => 'object',
					'properties' => (object) [],
				],
				'output_schema' => [
					'type'       => 'object',
					'properties' => [
						'success' => [ 'type' => 'boolean' ],
						'message' => [ 'type' => 'string' ],
					],
				],
				'meta'          => [
					'show_in_rest' => true,
					'mcp'          => [ 'public' => true ],
				],
			],
		];
	}

	public static function register(): void {
		if ( ! function_exists( 'wp_register_ability' ) ) {
			return;
		}

		$callbacks = [
			'awfah-debug/get-debug-status' => [ __CLASS__, 'execute_get_debug_status' ],
			'awfah-debug/read-debug-log'   => [ __CLASS__, 'execute_read_debug_log' ],
			'awfah-debug/get-error-source'  => [ __CLASS__, 'execute_get_error_source' ],
			'awfah-debug/clear-debug-log'   => [ __CLASS__, 'execute_clear_debug_log' ],
		];

		foreach ( self::get_abilities() as $ability ) {
			$config = $ability;
			$id     = $ability['id'];
			unset( $config['id'], $config['module'] );

			// Workaround: MCP ResourcesHandler calls execute() with no input.
			if ( isset( $config['meta']['uri'], $config['input_schema']['type'] ) && 'object' === $config['input_schema']['type'] ) {
				$config['input_schema']['default'] = (object) [];
			}

			wp_register_ability( $id, array_merge( $config, [
				'execute_callback'    => $callbacks[ $id ],
				'permission_callback' => static function () use ( $id ) {
					return \AtlasAiDev\Awfah\Plugin::check_ability_access( $id, 'manage_options' );
				},
			] ) );
		}
	}

	/**
	 * Resolve the path to the WordPress debug log file.
	 *
	 * @return string Absolute path to the debug log.
	 */
	public static function get_log_path(): string {
		// Check for user-configured custom path in settings.
		$settings    = \AtlasAiDev\Awfah\Plugin::get_settings();
		$custom_path = $settings['error_notification']['log_file_path'] ?? '';
		if ( '' !== $custom_path && file_exists( $custom_path ) ) {
			return $custom_path;
		}

		if ( defined( 'WP_DEBUG_LOG' ) && is_string( WP_DEBUG_LOG ) && '' !== WP_DEBUG_LOG ) {
			return WP_DEBUG_LOG;
		}

		return WP_CONTENT_DIR . '/debug.log';
	}

	/**
	 * Validate that a file path is within allowed directories (plugins or themes).
	 *
	 * @param string $file_path Absolute file path to validate.
	 * @return array{valid: bool, source_type: string, source_name: string, error: string}
	 */
	public static function validate_file_path( string $file_path ): array {
		$result = [
			'valid'       => false,
			'source_type' => '',
			'source_name' => '',
			'error'       => '',
		];

		// Normalize path separators.
		$file_path = wp_normalize_path( $file_path );

		$real_path = realpath( $file_path );
		if ( false === $real_path ) {
			$result['error'] = __( 'File does not exist.', 'ai-workflow-automation-ai-agent-hub' );
			return $result;
		}

		$real_path   = wp_normalize_path( $real_path );
		$plugins_dir = wp_normalize_path( WP_CONTENT_DIR . '/plugins/' );
		$themes_dir  = wp_normalize_path( WP_CONTENT_DIR . '/themes/' );

		if ( 0 === strpos( $real_path, $plugins_dir ) ) {
			$result['valid']       = true;
			$result['source_type'] = 'plugin';
			$relative              = substr( $real_path, strlen( $plugins_dir ) );
			$parts                 = explode( '/', $relative, 2 );
			$result['source_name'] = $parts[0] ?? '';
		} elseif ( 0 === strpos( $real_path, $themes_dir ) ) {
			$result['valid']       = true;
			$result['source_type'] = 'theme';
			$relative              = substr( $real_path, strlen( $themes_dir ) );
			$parts                 = explode( '/', $relative, 2 );
			$result['source_name'] = $parts[0] ?? '';
		} else {
			$result['error'] = __( 'Access denied. Only files within wp-content/plugins/ or wp-content/themes/ are allowed.', 'ai-workflow-automation-ai-agent-hub' );
		}

		return $result;
	}

	/**
	 * Determine source type and name from a file path string (may not exist on disk).
	 *
	 * @param string $file_path File path from a log entry.
	 * @return array{source_type: string, source_name: string}
	 */
	public static function identify_source( string $file_path ): array {
		$file_path   = wp_normalize_path( $file_path );
		$plugins_dir = wp_normalize_path( WP_CONTENT_DIR . '/plugins/' );
		$themes_dir  = wp_normalize_path( WP_CONTENT_DIR . '/themes/' );

		if ( false !== strpos( $file_path, $plugins_dir ) ) {
			$relative = substr( $file_path, strpos( $file_path, $plugins_dir ) + strlen( $plugins_dir ) );
			$parts    = explode( '/', $relative, 2 );
			return [ 'source_type' => 'plugin', 'source_name' => $parts[0] ?? '' ];
		}

		if ( false !== strpos( $file_path, $themes_dir ) ) {
			$relative = substr( $file_path, strpos( $file_path, $themes_dir ) + strlen( $themes_dir ) );
			$parts    = explode( '/', $relative, 2 );
			return [ 'source_type' => 'theme', 'source_name' => $parts[0] ?? '' ];
		}

		// MU-Plugins detection.
		$mu_plugins_dir = wp_normalize_path( WP_CONTENT_DIR . '/mu-plugins/' );
		if ( false !== strpos( $file_path, $mu_plugins_dir ) ) {
			$relative = substr( $file_path, strpos( $file_path, $mu_plugins_dir ) + strlen( $mu_plugins_dir ) );
			$parts    = explode( '/', $relative, 2 );
			$name     = $parts[0] ?? '';
			// Single-file mu-plugins: use filename without extension as name.
			if ( '' !== $name && false === strpos( $name, '/' ) && false !== strpos( $name, '.php' ) ) {
				$name = str_replace( '.php', '', $name );
			}
			return [ 'source_type' => 'mu-plugin', 'source_name' => $name ];
		}

		// Drop-in detection: known WP drop-in files in wp-content root.
		$content_dir = wp_normalize_path( WP_CONTENT_DIR . '/' );
		$drop_ins    = [
			'advanced-cache.php', 'db.php', 'db-error.php', 'install.php',
			'maintenance.php', 'object-cache.php', 'php-error.php',
			'fatal-error-handler.php', 'sunrise.php',
		];
		if ( 0 === strpos( $file_path, $content_dir ) ) {
			$relative = substr( $file_path, strlen( $content_dir ) );
			// Drop-ins are directly in wp-content, not in subdirectories.
			if ( false === strpos( $relative, '/' ) && in_array( $relative, $drop_ins, true ) ) {
				return [ 'source_type' => 'drop-in', 'source_name' => str_replace( '.php', '', $relative ) ];
			}
		}

		// Check using relative paths for cross-platform compatibility.
		if ( false !== strpos( $file_path, '/plugins/' ) ) {
			$pos      = strpos( $file_path, '/plugins/' ) + strlen( '/plugins/' );
			$relative = substr( $file_path, $pos );
			$parts    = explode( '/', $relative, 2 );
			return [ 'source_type' => 'plugin', 'source_name' => $parts[0] ?? '' ];
		}

		if ( false !== strpos( $file_path, '/themes/' ) ) {
			$pos      = strpos( $file_path, '/themes/' ) + strlen( '/themes/' );
			$relative = substr( $file_path, $pos );
			$parts    = explode( '/', $relative, 2 );
			return [ 'source_type' => 'theme', 'source_name' => $parts[0] ?? '' ];
		}

		if ( false !== strpos( $file_path, '/mu-plugins/' ) ) {
			$pos      = strpos( $file_path, '/mu-plugins/' ) + strlen( '/mu-plugins/' );
			$relative = substr( $file_path, $pos );
			$parts    = explode( '/', $relative, 2 );
			$name     = $parts[0] ?? '';
			if ( '' !== $name && false !== strpos( $name, '.php' ) ) {
				$name = str_replace( '.php', '', $name );
			}
			return [ 'source_type' => 'mu-plugin', 'source_name' => $name ];
		}

		return [ 'source_type' => 'core', 'source_name' => '' ];
	}

	/**
	 * Read the last N lines from a file efficiently using SplFileObject.
	 *
	 * @param string $file_path Absolute path to the file.
	 * @param int    $num_lines Number of lines to read from the end.
	 * @return string[] Array of lines.
	 */
	public static function read_last_lines( string $file_path, int $num_lines ): array {
		if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
			return [];
		}

		try {
			$file = new \SplFileObject( $file_path, 'r' );
			$file->seek( PHP_INT_MAX );
			$total_lines = $file->key();

			if ( 0 === $total_lines ) {
				return [];
			}

			$start = max( 0, $total_lines - $num_lines );
			$lines = [];

			$file->seek( $start );
			while ( ! $file->eof() ) {
				$line = $file->current();
				if ( '' !== trim( $line ) ) {
					$lines[] = rtrim( $line );
				}
				$file->next();
			}

			return $lines;
		} catch ( \Throwable $e ) {
			return [];
		}
	}

	/**
	 * Parse a debug log line into structured data.
	 *
	 * @param string $line Raw log line.
	 * @return array|null Parsed entry or null if not a recognized PHP error.
	 */
	public static function parse_log_line( string $line ): ?array {
		// Match standard PHP error format:
		// [DD-Mon-YYYY HH:MM:SS UTC] PHP Fatal error:  Message in /path/to/file.php on line 123
		$pattern = '/^\[([^\]]+)\]\s+PHP\s+(Fatal error|Warning|Notice|Deprecated|Parse error|Strict Standards):\s*(.+?)\s+in\s+(.+?)\s+on\s+line\s+(\d+)/i';

		if ( ! preg_match( $pattern, $line, $matches ) ) {
			// Also try matching stack traces and other PHP error formats.
			$pattern_simple = '/^\[([^\]]+)\]\s+PHP\s+(Fatal error|Warning|Notice|Deprecated|Parse error|Strict Standards):\s*(.+)/i';
			if ( ! preg_match( $pattern_simple, $line, $matches ) ) {
				return null;
			}

			return [
				'timestamp'   => $matches[1],
				'severity'    => self::normalize_severity( $matches[2] ),
				'message'     => trim( $matches[3] ),
				'file'        => '',
				'line'        => 0,
				'source_type' => 'unknown',
				'source_name' => '',
				'raw'         => $line,
			];
		}

		$source = self::identify_source( $matches[4] );

		return [
			'timestamp'   => $matches[1],
			'severity'    => self::normalize_severity( $matches[2] ),
			'message'     => trim( $matches[3] ),
			'file'        => $matches[4],
			'line'        => (int) $matches[5],
			'source_type' => $source['source_type'],
			'source_name' => $source['source_name'],
			'raw'         => $line,
		];
	}

	/**
	 * Normalize severity string to a consistent value.
	 *
	 * @param string $severity Raw severity from log.
	 * @return string Normalized severity.
	 */
	private static function normalize_severity( string $severity ): string {
		$severity = strtolower( trim( $severity ) );

		$map = [
			'fatal error'      => 'fatal',
			'parse error'      => 'fatal',
			'warning'          => 'warning',
			'notice'           => 'notice',
			'deprecated'       => 'deprecated',
			'strict standards' => 'notice',
		];

		return $map[ $severity ] ?? 'unknown';
	}

	/**
	 * Execute: Get debug configuration and log file status.
	 */
	public static function execute_get_debug_status( $input = null ): array {
		$log_path   = self::get_log_path();
		$log_exists = file_exists( $log_path );

		$wp_debug_log_value = 'false';
		if ( defined( 'WP_DEBUG_LOG' ) ) {
			if ( is_string( WP_DEBUG_LOG ) ) {
				$wp_debug_log_value = WP_DEBUG_LOG;
			} else {
				$wp_debug_log_value = WP_DEBUG_LOG ? 'true' : 'false';
			}
		}

		return [
			'wp_debug'       => defined( 'WP_DEBUG' ) && WP_DEBUG,
			'wp_debug_log'   => $wp_debug_log_value,
			'wp_debug_display' => defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY,
			'log_path'       => $log_path,
			'log_exists'     => $log_exists,
			'log_size'       => $log_exists ? (int) filesize( $log_path ) : 0,
			'log_size_human' => $log_exists ? size_format( filesize( $log_path ) ) : '0 B',
			'log_modified'   => $log_exists ? gmdate( 'Y-m-d H:i:s', filemtime( $log_path ) ) : '',
			'writable'       => $log_exists ? is_writable( $log_path ) : is_writable( dirname( $log_path ) ),
		];
	}

	/**
	 * Execute: Read and parse recent debug log entries.
	 */
	public static function execute_read_debug_log( $input = null ): array {
		$input = is_array( $input ) ? $input : [];

		$lines         = isset( $input['lines'] ) ? min( (int) $input['lines'], self::MAX_LINES ) : 100;
		$severity      = isset( $input['severity'] ) ? sanitize_text_field( $input['severity'] ) : 'all';
		$source_filter = isset( $input['source_filter'] ) ? sanitize_text_field( $input['source_filter'] ) : 'all';

		$log_path = self::get_log_path();

		if ( ! file_exists( $log_path ) ) {
			return [
				'entries' => [],
				'total'   => 0,
				'message' => __( 'Debug log file does not exist. Ensure WP_DEBUG and WP_DEBUG_LOG are enabled in wp-config.php.', 'ai-workflow-automation-ai-agent-hub' ),
			];
		}

		$raw_lines = self::read_last_lines( $log_path, $lines );
		$entries   = [];

		foreach ( $raw_lines as $line ) {
			$parsed = self::parse_log_line( $line );
			if ( null === $parsed ) {
				continue;
			}

			// Exclude core errors — only plugin and theme errors.
			if ( 'core' === $parsed['source_type'] || 'unknown' === $parsed['source_type'] ) {
				continue;
			}

			// Filter by severity.
			if ( 'all' !== $severity && $parsed['severity'] !== $severity ) {
				continue;
			}

			// Filter by source type.
			if ( 'plugins' === $source_filter && 'plugin' !== $parsed['source_type'] ) {
				continue;
			}
			if ( 'themes' === $source_filter && 'theme' !== $parsed['source_type'] ) {
				continue;
			}

			$entries[] = $parsed;
		}

		return [
			'entries' => $entries,
			'total'   => count( $entries ),
		];
	}

	/**
	 * Execute: Read source code around an error location.
	 */
	public static function execute_get_error_source( $input = null ): array {
		$input = is_array( $input ) ? $input : [];

		$file_path     = isset( $input['file_path'] ) ? (string) $input['file_path'] : '';
		$line_number   = isset( $input['line_number'] ) ? (int) $input['line_number'] : 0;
		$context_lines = isset( $input['context_lines'] ) ? min( (int) $input['context_lines'], 50 ) : 10;

		if ( '' === $file_path || 0 === $line_number ) {
			return [ 'error' => __( 'file_path and line_number are required.', 'ai-workflow-automation-ai-agent-hub' ) ];
		}

		// Security: Validate path is within plugins/ or themes/.
		$validation = self::validate_file_path( $file_path );
		if ( ! $validation['valid'] ) {
			return [ 'error' => $validation['error'] ];
		}

		$real_path = wp_normalize_path( realpath( $file_path ) );

		if ( ! is_readable( $real_path ) ) {
			return [ 'error' => __( 'File is not readable.', 'ai-workflow-automation-ai-agent-hub' ) ];
		}

		$file_lines = file( $real_path );
		if ( false === $file_lines ) {
			return [ 'error' => __( 'Could not read file.', 'ai-workflow-automation-ai-agent-hub' ) ];
		}

		$total_lines = count( $file_lines );
		$start       = max( 0, $line_number - $context_lines - 1 );
		$end         = min( $total_lines, $line_number + $context_lines );

		$source_code = '';
		for ( $i = $start; $i < $end; $i++ ) {
			$line_num  = $i + 1;
			$marker    = ( $line_num === $line_number ) ? ' >>> ' : '     ';
			$source_code .= sprintf( '%s%4d | %s', $marker, $line_num, $file_lines[ $i ] );
		}

		return [
			'file_path'   => $real_path,
			'line_number' => $line_number,
			'total_lines' => $total_lines,
			'source_code' => $source_code,
			'source_type' => $validation['source_type'],
			'source_name' => $validation['source_name'],
		];
	}

	/**
	 * Execute: Clear (truncate) the debug log file.
	 */
	public static function execute_clear_debug_log( $input = null ): array {
		$log_path = self::get_log_path();

		if ( ! file_exists( $log_path ) ) {
			return [
				'success' => false,
				'message' => __( 'Debug log file does not exist.', 'ai-workflow-automation-ai-agent-hub' ),
			];
		}

		if ( ! is_writable( $log_path ) ) {
			return [
				'success' => false,
				'message' => __( 'Debug log file is not writable.', 'ai-workflow-automation-ai-agent-hub' ),
			];
		}

		$size_before = filesize( $log_path );
		$result      = file_put_contents( $log_path, '' );

		if ( false === $result ) {
			return [
				'success' => false,
				'message' => __( 'Failed to clear debug log.', 'ai-workflow-automation-ai-agent-hub' ),
			];
		}

		return [
			'success'       => true,
			'message'       => sprintf(
				/* translators: %s: human-readable file size */
				__( 'Debug log cleared successfully. Previous size: %s.', 'ai-workflow-automation-ai-agent-hub' ),
				size_format( $size_before )
			),
			'previous_size' => (int) $size_before,
		];
	}
}
