<?php

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

class Sabriel_Module_Traffico extends Sabriel_Module {

	const PREFISSO_OPZIONE     = 'sabriel_traffico_';
	const GIORNI_CONSERVAZIONE = 90;
	const MAX_PAGINE_TRACCIATE = 300;

	const MAX_VISITATORI_GIORNO = 2000;

	const MAX_LUNGHEZZA_VISITATORE = 32;

	const MAX_EVENTI_ORA_IP = 1000;

	const MAX_BLOCCHI_ORA_IP = 20;

	public function get_id() {
		return 'traffico';
	}

	public function register_hooks() {
		add_action( 'rest_api_init', [ $this, 'register_routes' ] );
		add_action( Sabriel_Module_Core::CLEANUP_HOOK, [ $this, 'pulisci_vecchi' ] );
	}

	public function register_routes() {
		register_rest_route( 'sabriel/v1', '/traffico/log', [
			'methods'             => 'POST',
			'callback'            => [ $this, 'rest_log' ],
			'permission_callback' => '__return_true',
		] );
	}

	/**
	 * ⚠ IL FRENO PER IP E' STATO TOLTO, ED E' UNA CONSEGUENZA DEL FIX QUI SOTTO.
	 * Serviva a non far riscrivere troppe volte una riga da decine di KB. Adesso
	 * che si scrive un numero solo, quel freno non protegge piu' niente e in
	 * cambio FALSA il conteggio: sotto attacco l'Analitica si fermava a
	 * MAX_BLOCCHI_ORA_IP e mostrava «20» quando i blocchi erano cinquanta o
	 * cinquemila, cioe' mentiva proprio nel giorno in cui quel numero serve.
	 * MISURATO: 50 blocchi -> ne contava 20. Ora ne conta 50.
	 * ⚠ La costante MAX_BLOCCHI_ORA_IP resta dichiarata ma non e' piu' usata:
	 * NON rimuoverla senza controllare, potrebbe servire a un aggiornamento che
	 * arriva da una versione vecchia.
	 */
	public static function registra_blocco_sicurezza( $motivo = '' ) {
		/*
		 * ⚠ IL CONTEGGIO STA IN UNA RIGA A PARTE, ED E' IL PUNTO DI TUTTO.
		 * Prima viveva dentro la riga del giorno, insieme all'elenco dei visitatori
		 * e delle pagine: ai tetti di questo file quella riga arriva a decine di KB e
		 * veniva RILETTA E RISCRITTA PER INTERO a ogni singolo blocco. Ma questa
		 * funzione la chiamano proprio le difese, cioe' i punti che qualcuno colpisce
		 * a raffica: rifiutare costava al sito piu' che accettare.
		 * Qui si scrive un numero solo: ~50 byte invece di decine di KB.
		 */
		$chiave = self::chiave_blocchi( gmdate( 'Y-m-d' ) );
		update_option( $chiave, (int) get_option( $chiave, 0 ) + 1, false );
	}

	/**
	 * Nome della riga che conta i blocchi di sicurezza di un giorno.
	 *
	 * ⚠ LA DATA VA PRIMA DEL SUFFISSO, E NON E' ESTETICA: pulisci_vecchi()
	 * confronta i nomi come TESTO per capire quali giorni sono scaduti. Con il
	 * suffisso in mezzo (sabriel_traffico_blocchi_2026-01-01) quel confronto non
	 * funzionerebbe piu' e queste righe non verrebbero cancellate MAI, contro i
	 * giorni di conservazione promessi.
	 */
	private static function chiave_blocchi( $data ) {
		return self::PREFISSO_OPZIONE . $data . '_blocchi';
	}

	public function rest_log( WP_REST_Request $req ) {
		if ( ! Sabriel_Module_Settings::get( 'statistiche_attive' ) ) {
			return new WP_REST_Response( [ 'ok' => false ], 200 );
		}
		$body      = json_decode( (string) $req->get_body(), true ) ?? [];
		$tipo      = ( 'chat' === ( $body['tipo'] ?? '' ) ) ? 'chat' : 'pagina';

		$visitante = mb_substr( sanitize_text_field( $body['v'] ?? '' ), 0, self::MAX_LUNGHEZZA_VISITATORE );
		if ( '' === $visitante ) {
			return new WP_REST_Response( [ 'ok' => false ], 200 );
		}

		$sab_chiave_freno = 'sabriel_ai_traf_rl_' . md5( Sabriel_Helpers::get_client_ip() );
		$sab_eventi       = (int) get_transient( $sab_chiave_freno );
		if ( $sab_eventi >= self::MAX_EVENTI_ORA_IP ) {
			return new WP_REST_Response( [ 'ok' => false ], 200 );
		}
		set_transient( $sab_chiave_freno, $sab_eventi + 1, HOUR_IN_SECONDS );

		$chiave_oggi = self::PREFISSO_OPZIONE . gmdate( 'Y-m-d' );
		$dati        = get_option( $chiave_oggi, [ 'visite' => 0, 'pagine' => [], 'visitatori' => [], 'chat_aperte' => 0 ] );
		if ( ! is_array( $dati ) ) {
			$dati = [ 'visite' => 0, 'pagine' => [], 'visitatori' => [], 'chat_aperte' => 0 ];
		}

		if ( 'chat' === $tipo ) {
			$dati['chat_aperte'] = (int) ( $dati['chat_aperte'] ?? 0 ) + 1;
		} else {
			$percorso = sanitize_text_field( $body['percorso'] ?? '' );
			if ( '' === $percorso ) {
				return new WP_REST_Response( [ 'ok' => false ], 200 );
			}
			$percorso       = (string) strtok( $percorso, '?' );
			$percorso       = mb_substr( $percorso, 0, 200 );
			if ( '' === $percorso ) {

				return new WP_REST_Response( [ 'ok' => false ], 200 );
			}
			$dati['visite'] = (int) ( $dati['visite'] ?? 0 ) + 1;
			if ( count( $dati['pagine'] ?? [] ) < self::MAX_PAGINE_TRACCIATE || isset( $dati['pagine'][ $percorso ] ) ) {
				$dati['pagine'][ $percorso ] = (int) ( $dati['pagine'][ $percorso ] ?? 0 ) + 1;
			}
		}
		if ( ! in_array( $visitante, $dati['visitatori'] ?? [], true ) ) {

			if ( count( $dati['visitatori'] ?? [] ) < self::MAX_VISITATORI_GIORNO ) {
				$dati['visitatori'][] = $visitante;
			}
		}
		update_option( $chiave_oggi, $dati, false );
		return new WP_REST_Response( [ 'ok' => true ], 200 );
	}

	public static function statistiche( $giorni = 30 ) {
		static $gia_calcolate = [];
		$giorni = (int) $giorni;
		if ( isset( $gia_calcolate[ $giorni ] ) ) {
			return $gia_calcolate[ $giorni ];
		}
		$visite_totali    = 0;
		$chat_aperte_tot  = 0;
		$blocchi_tot      = 0;
		$visitatori_unici = [];
		$pagine_tally     = [];
		$per_giorno       = [];
		$unici_parziale   = false;
		for ( $i = $giorni - 1; $i >= 0; $i-- ) {
			$data_chiave = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
			$dati        = get_option( self::PREFISSO_OPZIONE . $data_chiave, null );
			/* La riga a parte dei blocchi si somma SEMPRE, anche per i giorni in cui
			   non c'e' stata nessuna visita: un attacco puo' benissimo essere l'unica
			   cosa successa quel giorno. */
			$blocchi_tot += (int) get_option( self::chiave_blocchi( $data_chiave ), 0 );
			$visite_oggi = 0;
			if ( is_array( $dati ) ) {
				$visite_oggi      = (int) ( $dati['visite'] ?? 0 );
				$visite_totali   += $visite_oggi;
				$chat_aperte_tot += (int) ( $dati['chat_aperte'] ?? 0 );
				/* ⚠ SI SOMMANO DUE POSTI: la riga nuova e il vecchio campo dentro la riga
				   del giorno. Senza il secondo, chi aggiorna il plugin vedrebbe azzerarsi
				   di colpo i numeri dei giorni gia' passati. */
				$blocchi_tot     += (int) ( $dati['blocchi_sicurezza'] ?? 0 );
				$visitatori_giorno = (array) ( $dati['visitatori'] ?? [] );
				if ( count( $visitatori_giorno ) >= self::MAX_VISITATORI_GIORNO ) {
					$unici_parziale = true;
				}
				foreach ( $visitatori_giorno as $v ) {
					$visitatori_unici[ $v ] = true;
				}
				foreach ( (array) ( $dati['pagine'] ?? [] ) as $pagina => $conteggio ) {
					$pagine_tally[ $pagina ] = ( $pagine_tally[ $pagina ] ?? 0 ) + (int) $conteggio;
				}
			}
			$per_giorno[] = [ 'data' => $data_chiave, 'visite' => $visite_oggi ];
		}
		arsort( $pagine_tally );
		$gia_calcolate[ $giorni ] = [
			'visite_totali'     => $visite_totali,
			'chat_aperte'       => $chat_aperte_tot,
			'blocchi_sicurezza' => $blocchi_tot,
			'visitatori_unici'  => count( $visitatori_unici ),
			'visitatori_parziale' => $unici_parziale,
			'pagine_top'        => array_slice( $pagine_tally, 0, 8, true ),
			'per_giorno'        => $per_giorno,
			'ha_dati'           => $visite_totali > 0 || $chat_aperte_tot > 0,
		];
		return $gia_calcolate[ $giorni ];
	}

	public function pulisci_vecchi() {
		global $wpdb;
		$soglia = self::PREFISSO_OPZIONE . gmdate( 'Y-m-d', strtotime( '-' . self::GIORNI_CONSERVAZIONE . ' days' ) );

		$nomi = $wpdb->get_col( $wpdb->prepare(
			"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s AND option_name < %s LIMIT 500",
			$wpdb->esc_like( self::PREFISSO_OPZIONE ) . '%',
			$soglia
		) );
		foreach ( $nomi as $nome ) {
			delete_option( $nome );
		}
	}
}
