<?php

namespace AtlasAiDev\Awfah\Service;

use AtlasAiDev\Awfah\Abilities\Debug_Log_Abilities;

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

/**
 * Tracks PHP error occurrences and groups them by unique signature.
 *
 * Errors are grouped by md5(file:line:message). Each group stores the count,
 * first/last seen timestamps, severity, and source info. Data persists in
 * the awfah_error_tracker option. Max 200 groups with FIFO eviction.
 *
 * @package AtlasAiDev\Awfah\Service
 */
class Error_Tracker {

	/** @var self|null */
	private static $instance = null;

	/** Maximum number of error groups to store. */
	const MAX_GROUPS = 200;

	/** Option key for persisted error groups. */
	const OPTION_KEY = 'awfah_error_tracker';

	/** Option key for daily error counts. */
	const DAILY_COUNTS_KEY = 'awfah_error_daily_counts';

	/** Maximum days of daily counts to retain. */
	const MAX_DAILY_DAYS = 30;

	/**
	 * Get singleton instance.
	 */
	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Track an error occurrence — upsert into the groups store.
	 *
	 * @param array $error_data Error details with keys: message, file, line, severity, source_type, source_name.
	 */
	public function track_error( array $error_data ): void {
		$file    = $error_data['file'] ?? '';
		$line    = $error_data['line'] ?? 0;
		$message = $error_data['message'] ?? '';

		if ( '' === $file || '' === $message ) {
			return;
		}

		$hash   = md5( $file . ':' . $line . ':' . $message );
		$groups = $this->get_raw_groups();
		$now    = gmdate( 'c' );

		if ( isset( $groups[ $hash ] ) ) {
			$groups[ $hash ]['count']++;
			$groups[ $hash ]['last_seen'] = $now;
		} else {
			$groups[ $hash ] = [
				'hash'        => $hash,
				'message'     => substr( $message, 0, 500 ),
				'file'        => $file,
				'line'        => (int) $line,
				'severity'    => $error_data['severity'] ?? 'unknown',
				'source_type' => $error_data['source_type'] ?? 'unknown',
				'source_name' => $error_data['source_name'] ?? '',
				'count'       => 1,
				'first_seen'  => $now,
				'last_seen'   => $now,
			];
		}

		// Enforce max groups — evict oldest by last_seen.
		if ( count( $groups ) > self::MAX_GROUPS ) {
			uasort( $groups, function ( $a, $b ) {
				return strcmp( $b['last_seen'], $a['last_seen'] );
			} );
			$groups = array_slice( $groups, 0, self::MAX_GROUPS, true );
		}

		update_option( self::OPTION_KEY, $groups, false );

		// Also update daily count.
		$this->increment_daily_count( $error_data['severity'] ?? 'unknown' );
	}

	/**
	 * Get error groups sorted and formatted.
	 *
	 * @param string $sort  Sort field: 'count' or 'last_seen'. Default 'count'.
	 * @param int    $limit Max groups to return. Default 50.
	 * @return array Sorted error groups.
	 */
	public function get_groups( string $sort = 'count', int $limit = 50 ): array {
		$groups = array_values( $this->get_raw_groups() );

		if ( 'count' === $sort ) {
			usort( $groups, function ( $a, $b ) {
				return $b['count'] - $a['count'];
			} );
		} else {
			usort( $groups, function ( $a, $b ) {
				return strcmp( $b['last_seen'], $a['last_seen'] );
			} );
		}

		return array_slice( $groups, 0, $limit );
	}

	/**
	 * Clear all tracked error groups.
	 */
	public function clear_groups(): void {
		delete_option( self::OPTION_KEY );
	}

	/**
	 * Remove a specific error group by hash (e.g., after fixing the error).
	 *
	 * @param string $hash Error hash (md5 of file:line:message).
	 * @return bool True if the group was found and removed.
	 */
	public function mark_fixed( string $hash ): bool {
		$groups = $this->get_raw_groups();
		if ( ! isset( $groups[ $hash ] ) ) {
			return false;
		}
		unset( $groups[ $hash ] );
		update_option( self::OPTION_KEY, $groups, false );
		return true;
	}

	/**
	 * Get total error count across all groups.
	 */
	public function get_total_count(): int {
		$groups = $this->get_raw_groups();
		$total  = 0;
		foreach ( $groups as $group ) {
			$total += $group['count'];
		}
		return $total;
	}

	/**
	 * Get raw groups from the database.
	 *
	 * @return array
	 */
	private function get_raw_groups(): array {
		$groups = get_option( self::OPTION_KEY, [] );
		return is_array( $groups ) ? $groups : [];
	}

	// ── Daily Counts (for Phase 4 trend tracking) ──────────────────

	/**
	 * Increment today's error count.
	 *
	 * @param string $severity Error severity.
	 */
	private function increment_daily_count( string $severity ): void {
		$counts = get_option( self::DAILY_COUNTS_KEY, [] );
		if ( ! is_array( $counts ) ) {
			$counts = [];
		}

		$today = gmdate( 'Y-m-d' );

		if ( ! isset( $counts[ $today ] ) ) {
			$counts[ $today ] = [ 'total' => 0, 'fatal' => 0, 'warning' => 0, 'notice' => 0, 'deprecated' => 0 ];
		}

		$counts[ $today ]['total']++;
		if ( isset( $counts[ $today ][ $severity ] ) ) {
			$counts[ $today ][ $severity ]++;
		}

		// Prune old days.
		$cutoff = gmdate( 'Y-m-d', strtotime( '-' . self::MAX_DAILY_DAYS . ' days' ) );
		foreach ( array_keys( $counts ) as $date ) {
			if ( $date < $cutoff ) {
				unset( $counts[ $date ] );
			}
		}

		update_option( self::DAILY_COUNTS_KEY, $counts, false );
	}

	/**
	 * Get daily error counts for the last N days.
	 *
	 * @param int $days Number of days. Default 7.
	 * @return array Keyed by date string.
	 */
	public function get_daily_counts( int $days = 7 ): array {
		$counts = get_option( self::DAILY_COUNTS_KEY, [] );
		if ( ! is_array( $counts ) ) {
			return [];
		}

		$result = [];
		$empty  = [ 'total' => 0, 'fatal' => 0, 'warning' => 0, 'notice' => 0, 'deprecated' => 0 ];

		for ( $i = $days - 1; $i >= 0; $i-- ) {
			$date            = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
			$result[ $date ] = $counts[ $date ] ?? $empty;
		}

		return $result;
	}

	/**
	 * Get trend comparison: this week vs last week.
	 *
	 * @return array{ this_week: int, last_week: int, change_percent: float }
	 */
	public function get_weekly_trend(): array {
		$counts    = get_option( self::DAILY_COUNTS_KEY, [] );
		$this_week = 0;
		$last_week = 0;

		for ( $i = 0; $i < 7; $i++ ) {
			$date = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
			$this_week += $counts[ $date ]['total'] ?? 0;
		}
		for ( $i = 7; $i < 14; $i++ ) {
			$date = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
			$last_week += $counts[ $date ]['total'] ?? 0;
		}

		$change = 0 === $last_week ? ( $this_week > 0 ? 100.0 : 0.0 )
			: round( ( ( $this_week - $last_week ) / $last_week ) * 100, 1 );

		return [
			'this_week'      => $this_week,
			'last_week'      => $last_week,
			'change_percent' => $change,
		];
	}

	/**
	 * Clear daily counts.
	 */
	public function clear_daily_counts(): void {
		delete_option( self::DAILY_COUNTS_KEY );
	}
}
