<?php
/**
 * Redirect Health grade: a single letter (A–F) site owners can screenshot.
 *
 * Pure logic, unit tested. Rules (the Zen Plugins grade formula):
 *  - Nothing verified yet          → 'pending'
 *  - 0 failures, 0 warnings        → A
 *  - 0 failures, some warnings     → B
 *  - failures ≤ 5% of verified     → C
 *  - failures ≤ 15%                → D
 *  - more                          → F
 *  - Stale data (last verification cycle older than twice the re-verify
 *    interval, minimum 2 days) degrades the grade one letter — a grade
 *    you can't trust shouldn't look pristine.
 *
 * @package RedirectZen
 */

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

class RDZ_Grade {

	const LETTERS = array( 'A', 'B', 'C', 'D', 'F' );

	/**
	 * Computes the letter grade.
	 *
	 * @param array $counts             Verify-status counts (ok, fail, warn, pending, queued).
	 * @param int   $last_cycle         Unix timestamp of the last completed verification cycle (0 = never).
	 * @param int   $frequency_seconds  Re-verification interval in seconds (0 = manual only).
	 * @param int   $now                Current unix timestamp.
	 * @return string 'A'|'B'|'C'|'D'|'F'|'pending'
	 */
	public static function compute( $counts, $last_cycle, $frequency_seconds, $now ) {
		$ok   = isset( $counts['ok'] ) ? (int) $counts['ok'] : 0;
		$fail = isset( $counts['fail'] ) ? (int) $counts['fail'] : 0;
		$warn = isset( $counts['warn'] ) ? (int) $counts['warn'] : 0;

		$verified = $ok + $fail + $warn;

		if ( 0 === $verified || ! $last_cycle ) {
			return 'pending';
		}

		if ( 0 === $fail ) {
			$grade = ( 0 === $warn ) ? 'A' : 'B';
		} else {
			$ratio = $fail / $verified;
			if ( $ratio <= 0.05 ) {
				$grade = 'C';
			} elseif ( $ratio <= 0.15 ) {
				$grade = 'D';
			} else {
				$grade = 'F';
			}
		}

		if ( self::is_stale( $last_cycle, $frequency_seconds, $now ) ) {
			$grade = self::degrade( $grade );
		}

		return $grade;
	}

	/**
	 * Whether verification data is too old to trust. With manual-only
	 * verification (free plan) the allowance is fixed at 30 days so the
	 * grade never silently rots.
	 *
	 * @param int $last_cycle        Last cycle timestamp.
	 * @param int $frequency_seconds Re-verify interval (0 = manual only).
	 * @param int $now               Current timestamp.
	 * @return bool
	 */
	public static function is_stale( $last_cycle, $frequency_seconds, $now ) {
		if ( $frequency_seconds > 0 ) {
			$allowance = max( 2 * (int) $frequency_seconds, 2 * DAY_IN_SECONDS );
		} else {
			$allowance = 30 * DAY_IN_SECONDS;
		}

		return ( $now - (int) $last_cycle ) > $allowance;
	}

	/**
	 * One letter worse.
	 *
	 * @param string $grade Letter.
	 * @return string
	 */
	public static function degrade( $grade ) {
		$index = array_search( $grade, self::LETTERS, true );
		if ( false === $index ) {
			return $grade;
		}

		return self::LETTERS[ min( $index + 1, count( self::LETTERS ) - 1 ) ];
	}

	/**
	 * Visual tone for a grade (maps to zen-grade-- modifier classes).
	 *
	 * @param string $grade Letter or 'pending'.
	 * @return string good|warn|bad|pending
	 */
	public static function tone( $grade ) {
		if ( 'A' === $grade || 'B' === $grade ) {
			return 'good';
		}
		if ( 'C' === $grade ) {
			return 'warn';
		}
		if ( 'pending' === $grade ) {
			return 'pending';
		}

		return 'bad';
	}
}
