<?php
/**
 * Token Tracker Class
 *
 * Records API token usage in a custom database table
 * and provides aggregated statistics for the dashboard.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.0.20
 */

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

/**
 * Token usage tracker.
 */
class SmartyPress_AI_Token_Tracker {

	/**
	 * Database table name (without prefix).
	 */
	const TABLE_NAME = 'smartypress_ai_token_usage';

	/**
	 * Option key that stores the current DB schema version.
	 */
	const DB_VERSION_OPTION = 'smartypress_ai_token_usage_db_version';

	/**
	 * Current schema version. Bump when the schema changes.
	 */
	const DB_VERSION = '1.0';

	/**
	 * Maximum age of records in days before automatic cleanup.
	 */
	const RETENTION_DAYS = 90;

	/**
	 * Cron hook name for daily cleanup.
	 */
	const CLEANUP_CRON_HOOK = 'smartypress_ai_token_usage_cleanup';

	/**
	 * Current module context set before API calls.
	 *
	 * @var string
	 */
	private static string $current_module = '';

	/**
	 * Set the current module context for the next API call.
	 *
	 * @param string $module Module identifier (e.g. 'content-generation').
	 */
	public static function set_module( string $module ): void {
		self::$current_module = sanitize_text_field( $module );
	}

	/**
	 * Get and reset the current module context.
	 *
	 * @return string
	 */
	public static function get_module(): string {
		$module               = self::$current_module;
		self::$current_module = '';
		return $module;
	}

	/**
	 * Get the full table name including the WP prefix.
	 *
	 * @return string
	 */
	public static function table_name(): string {
		global $wpdb;
		return $wpdb->prefix . self::TABLE_NAME;
	}

	/**
	 * Create or update the database table.
	 * Safe to call multiple times (idempotent via dbDelta).
	 */
	public static function create_table(): void {
		global $wpdb;

		$table   = self::table_name();
		$charset = $wpdb->get_charset_collate();

		$sql = "CREATE TABLE {$table} (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			user_id bigint(20) unsigned NOT NULL DEFAULT 0,
			provider varchar(32) NOT NULL DEFAULT '',
			model varchar(128) NOT NULL DEFAULT '',
			module varchar(64) NOT NULL DEFAULT '',
			input_tokens int unsigned NOT NULL DEFAULT 0,
			output_tokens int unsigned NOT NULL DEFAULT 0,
			total_tokens int unsigned NOT NULL DEFAULT 0,
			estimated_cost decimal(10,6) NOT NULL DEFAULT 0.000000,
			pricing_source varchar(32) NOT NULL DEFAULT 'unknown',
			created_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY  (id),
			KEY idx_created_at (created_at),
			KEY idx_provider_model (provider, model)
		) {$charset};";

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );

		update_option( self::DB_VERSION_OPTION, self::DB_VERSION );
	}

	/**
	 * Maybe create/update the table if schema version changed.
	 */
	public static function maybe_create_table(): void {
		if ( get_option( self::DB_VERSION_OPTION ) !== self::DB_VERSION ) {
			self::create_table();
		}
	}

	/**
	 * Record token usage from an API response.
	 *
	 * @param array $data {
	 *     @type string $provider Provider identifier.
	 *     @type string $model    Model identifier.
	 *     @type string $module   Module context (optional, falls back to static context).
	 *     @type array  $usage    Raw usage array from API response.
	 * }
	 */
	public static function record( array $data ): void {
		global $wpdb;

		$provider = sanitize_text_field( $data['provider'] ?? '' );
		$model    = sanitize_text_field( $data['model'] ?? '' );
		$module   = sanitize_text_field( $data['module'] ?? '' );
		$usage    = is_array( $data['usage'] ?? null ) ? $data['usage'] : array();

		if ( empty( $module ) ) {
			$module = self::get_module();
		}

		$normalized = self::normalize_usage( $usage );

		$cost_result = SmartyPress_AI_Model_Pricing::calculate_cost(
			$provider,
			$model,
			$normalized['input_tokens'],
			$normalized['output_tokens'],
			$usage
		);

		$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			self::table_name(),
			array(
				'user_id'        => get_current_user_id(),
				'provider'       => $provider,
				'model'          => $model,
				'module'         => $module,
				'input_tokens'   => $normalized['input_tokens'],
				'output_tokens'  => $normalized['output_tokens'],
				'total_tokens'   => $normalized['total_tokens'],
				'estimated_cost' => $cost_result['cost'],
				'pricing_source' => $cost_result['source'],
				'created_at'     => gmdate( 'Y-m-d H:i:s' ),
			),
			array( '%d', '%s', '%s', '%s', '%d', '%d', '%d', '%f', '%s', '%s' )
		);
	}

	/**
	 * Normalize raw usage data from different provider formats.
	 *
	 * @param array $usage Raw usage array.
	 * @return array{input_tokens: int, output_tokens: int, total_tokens: int}
	 */
	private static function normalize_usage( array $usage ): array {
		$input  = (int) ( $usage['prompt_tokens'] ?? $usage['input_tokens'] ?? $usage['inputTextTokens'] ?? 0 );
		if ( empty( $usage['total_tokens'] ) ) {
			$input += (int) ( $usage['cache_creation_input_tokens'] ?? 0 );
			$input += (int) ( $usage['cache_read_input_tokens'] ?? 0 );
		}
		$output = (int) ( $usage['completion_tokens'] ?? $usage['output_tokens'] ?? $usage['completionTokens'] ?? 0 );
		$total  = (int) ( $usage['total_tokens'] ?? 0 );

		if ( 0 === $total && ( $input > 0 || $output > 0 ) ) {
			$total = $input + $output;
		}

		return array(
			'input_tokens'  => $input,
			'output_tokens' => $output,
			'total_tokens'  => $total,
		);
	}

	/**
	 * Get daily token usage statistics.
	 *
	 * @param int $days Number of days to look back (7, 14, or 30).
	 * @return array Associative array: date => { total_tokens, estimated_cost, by_model => { model => { input, output, cost } } }.
	 */
	public static function get_daily_stats( int $days = 30 ): array {
		global $wpdb;

		$days  = in_array( $days, array( 7, 14, 30 ), true ) ? $days : 30;
		$since = gmdate( 'Y-m-d 00:00:00', strtotime( "-{$days} days" ) );
		$table = self::table_name();

		$rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT DATE(created_at) AS day, model,
					SUM(input_tokens) AS input_sum,
					SUM(output_tokens) AS output_sum,
					SUM(total_tokens) AS total_sum,
					SUM(estimated_cost) AS cost_sum
				FROM %i
				WHERE created_at >= %s
				GROUP BY day, model
				ORDER BY day ASC, cost_sum DESC",
				$table,
				$since
			),
			ARRAY_A
		);

		$daily = array();

		foreach ( (array) $rows as $row ) {
			$day   = $row['day'];
			$model = $row['model'];

			if ( ! isset( $daily[ $day ] ) ) {
				$daily[ $day ] = array(
					'total_tokens'  => 0,
					'estimated_cost' => 0.0,
					'by_model'       => array(),
				);
			}

			$daily[ $day ]['total_tokens']   += (int) $row['total_sum'];
			$daily[ $day ]['estimated_cost'] += (float) $row['cost_sum'];
			$daily[ $day ]['by_model'][ $model ] = array(
				'input'  => (int) $row['input_sum'],
				'output' => (int) $row['output_sum'],
				'cost'   => (float) $row['cost_sum'],
			);
		}

		return $daily;
	}

	/**
	 * Get summary statistics for a period.
	 *
	 * @param int $days Number of days.
	 * @return array{total_tokens: int, total_cost: float, total_requests: int, top_models: array}
	 */
	public static function get_summary( int $days = 30 ): array {
		global $wpdb;

		$days  = in_array( $days, array( 7, 14, 30 ), true ) ? $days : 30;
		$since = gmdate( 'Y-m-d 00:00:00', strtotime( "-{$days} days" ) );
		$table = self::table_name();

		$totals = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT SUM(total_tokens) AS total_tokens,
						SUM(estimated_cost) AS total_cost,
						COUNT(*) AS total_requests
				FROM %i
				WHERE created_at >= %s",
				$table,
				$since
			),
			ARRAY_A
		);

		$top_models = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT model,
						SUM(total_tokens) AS tokens,
						SUM(estimated_cost) AS cost,
						COUNT(*) AS requests
				FROM %i
				WHERE created_at >= %s
				GROUP BY model
				ORDER BY tokens DESC
				LIMIT 10",
				$table,
				$since
			),
			ARRAY_A
		);

		return array(
			'total_tokens'   => (int) ( $totals['total_tokens'] ?? 0 ),
			'total_cost'     => (float) ( $totals['total_cost'] ?? 0 ),
			'total_requests' => (int) ( $totals['total_requests'] ?? 0 ),
			'top_models'     => array_map(
				function ( $row ) {
					return array(
						'model'    => $row['model'],
						'tokens'   => (int) $row['tokens'],
						'cost'     => (float) $row['cost'],
						'requests' => (int) $row['requests'],
					);
				},
				(array) $top_models
			),
		);
	}

	/**
	 * Delete records older than RETENTION_DAYS.
	 */
	public static function cleanup(): void {
		global $wpdb;

		$cutoff = gmdate( 'Y-m-d 00:00:00', strtotime( '-' . self::RETENTION_DAYS . ' days' ) );
		$table  = self::table_name();

		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare( 'DELETE FROM %i WHERE created_at < %s', $table, $cutoff )
		);
	}

	/**
	 * Schedule the daily cleanup cron event.
	 */
	public static function schedule_cleanup(): void {
		if ( ! wp_next_scheduled( self::CLEANUP_CRON_HOOK ) ) {
			wp_schedule_event( time(), 'daily', self::CLEANUP_CRON_HOOK );
		}
	}

	/**
	 * Unschedule the daily cleanup cron event.
	 */
	public static function unschedule_cleanup(): void {
		$timestamp = wp_next_scheduled( self::CLEANUP_CRON_HOOK );
		if ( $timestamp ) {
			wp_unschedule_event( $timestamp, self::CLEANUP_CRON_HOOK );
		}
	}

	/**
	 * Drop the table entirely (used during uninstall).
	 */
	public static function drop_table(): void {
		global $wpdb;
		$table = self::table_name();
		$wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
		delete_option( self::DB_VERSION_OPTION );
	}
}
