<?php
/**
 * Métricas del cuadro de pedidos (compromisos, prórrogas, retrasos, cumplimiento).
 *
 * @package Logistica
 */

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

/**
 * Agrega indicadores de entrega por semana o rango de fechas.
 */
class Logistica_Pedidos_Metrics {

	/** @var list<string> */
	const DELIVERED_STATES = array( 'entregado', 'finalizado' );

	/** @var list<string> */
	const CLOSED_STATES = array( 'entregado', 'finalizado', 'cancelado' );

	/**
	 * Resumen de la semana actual (por fecha de entrega comprometida).
	 *
	 * @param string $current_week_start Lunes Y-m-d.
	 * @return array<string, mixed>
	 */
	public static function get_week_summary( $current_week_start ) {
		$current_week_start = Logistica_Forming_Planning::week_start( $current_week_start );
		$current            = self::aggregate_week( $current_week_start, self::week_as_of_date( $current_week_start ) );

		return array(
			'current_week' => $current_week_start,
			'week_label'   => (string) ( $current['week_label'] ?? '' ),
			'current'      => $current,
		);
	}

	/**
	 * Comparación semana actual vs anterior (por fecha de entrega comprometida).
	 *
	 * @param string $current_week_start Lunes Y-m-d.
	 * @return array<string, mixed>
	 */
	public static function get_week_comparison( $current_week_start ) {
		$current_week_start = Logistica_Forming_Planning::week_start( $current_week_start );
		$previous_week_start = Logistica_Forming_Planning::week_start(
			gmdate( 'Y-m-d', strtotime( $current_week_start . ' -7 days UTC' ) )
		);

		$current  = self::aggregate_week( $current_week_start, self::week_as_of_date( $current_week_start ) );
		$previous = self::aggregate_week( $previous_week_start, self::week_as_of_date( $previous_week_start ) );

		return array(
			'current_week'     => $current_week_start,
			'previous_week'    => $previous_week_start,
			'comparison_label' => trim( ( $previous['week_label'] ?? '' ) . ' vs ' . ( $current['week_label'] ?? '' ) ),
			'current'          => $current,
			'previous'         => $previous,
			'delta'            => self::build_delta( $previous, $current ),
		);
	}

	/**
	 * Resumen por semanas dentro de un rango (para gráficos).
	 *
	 * @param string $date_from Y-m-d.
	 * @param string $date_to   Y-m-d.
	 * @return array<string, mixed>|\WP_Error
	 */
	public static function get_period_summary( $date_from, $date_to ) {
		$date_from = self::sanitize_date( $date_from );
		$date_to   = self::sanitize_date( $date_to );

		if ( ! $date_from || ! $date_to ) {
			return new WP_Error( 'invalid_dates', __( 'Indique fechas de inicio y fin válidas.', 'logistica' ) );
		}

		if ( $date_from > $date_to ) {
			return new WP_Error( 'invalid_range', __( 'La fecha inicial no puede ser posterior a la final.', 'logistica' ) );
		}

		$max_days = 366;
		$diff     = (int) floor( ( strtotime( $date_to . ' UTC' ) - strtotime( $date_from . ' UTC' ) ) / DAY_IN_SECONDS );
		if ( $diff > $max_days ) {
			return new WP_Error( 'range_too_long', __( 'El período no puede superar un año.', 'logistica' ) );
		}

		$week_start = Logistica_Forming_Planning::week_start( $date_from );
		$week_end   = Logistica_Forming_Planning::week_start( $date_to );
		$weeks      = array();

		while ( $week_start <= $week_end ) {
			$weeks[]     = self::aggregate_week( $week_start, self::week_as_of_date( $week_start, $date_to ) );
			$week_start = gmdate( 'Y-m-d', strtotime( $week_start . ' +7 days UTC' ) );
		}

		return array(
			'date_from' => $date_from,
			'date_to'   => $date_to,
			'weeks'     => $weeks,
		);
	}

	/**
	 * @param string $week_start Lunes Y-m-d.
	 * @param string $as_of_date Fecha de corte Y-m-d.
	 * @return array<string, mixed>
	 */
	public static function aggregate_week( $week_start, $as_of_date ) {
		$week_start = Logistica_Forming_Planning::week_start( $week_start );
		$days       = Logistica_Forming_Planning::week_days( $week_start );
		$week_end   = ! empty( $days ) ? (string) ( end( $days )['date'] ?? $week_start ) : $week_start;
		$lines      = self::fetch_lines_for_range( $week_start, $week_end );

		$metrics = self::empty_metrics();
		$metrics['semana_inicio'] = $week_start;
		$metrics['semana_fin']    = $week_end;
		$metrics['week_label']    = self::format_week_label( $week_start, $week_end );

		foreach ( $lines as $line ) {
			$row = self::classify_line( $line, $as_of_date );
			if ( ! $row['committed'] ) {
				continue;
			}

			++$metrics['comprometidos'];
			if ( $row['prorroga'] ) {
				++$metrics['prorrogas'];
			}
			if ( $row['retraso'] ) {
				++$metrics['retrasos'];
			}
			if ( $row['a_tiempo'] ) {
				++$metrics['entregas_a_tiempo'];
			}
			if ( $row['entregado'] ) {
				++$metrics['entregados'];
			}
		}

		if ( $metrics['comprometidos'] > 0 ) {
			$metrics['cumplimiento_pct'] = round( ( $metrics['entregas_a_tiempo'] / $metrics['comprometidos'] ) * 100, 1 );
		}

		return $metrics;
	}

	/**
	 * @param string $week_start Lunes.
	 * @param string $week_end   Domingo.
	 * @return list<array<string, mixed>>
	 */
	private static function fetch_lines_for_range( $week_start, $week_end ) {
		global $wpdb;

		$lineas_table  = Logistica_Database::table( 'pedidos_ordenes_lineas' );
		$ordenes_table = Logistica_Database::table( 'pedidos_ordenes_compra' );

		$sql = "SELECT l.id, l.estado, l.fecha_entrega, l.fecha_prorroga, l.cantidad, l.cantidad_entregada
			FROM {$lineas_table} l
			INNER JOIN {$ordenes_table} o ON o.id = l.orden_id
			WHERE (
				(l.fecha_prorroga IS NOT NULL AND l.fecha_prorroga <> '0000-00-00' AND l.fecha_prorroga BETWEEN %s AND %s)
				OR (
					(l.fecha_prorroga IS NULL OR l.fecha_prorroga = '0000-00-00')
					AND l.fecha_entrega IS NOT NULL
					AND l.fecha_entrega <> '0000-00-00'
					AND l.fecha_entrega BETWEEN %s AND %s
				)
			)";

		$rows = $wpdb->get_results(
			$wpdb->prepare( $sql, $week_start, $week_end, $week_start, $week_end ),
			ARRAY_A
		);

		return is_array( $rows ) ? $rows : array();
	}

	/**
	 * @param array<string, mixed> $line      Fila línea OC.
	 * @param string               $as_of_date Y-m-d.
	 * @return array<string, bool>
	 */
	private static function classify_line( $line, $as_of_date ) {
		$estado      = sanitize_key( (string) ( $line['estado'] ?? '' ) );
		$fecha_entrega  = self::sanitize_date( (string) ( $line['fecha_entrega'] ?? '' ) );
		$fecha_prorroga = self::sanitize_date( (string) ( $line['fecha_prorroga'] ?? '' ) );
		$effective      = $fecha_prorroga ?: $fecha_entrega;
		$has_prorroga   = '' !== $fecha_prorroga;
		$entregado      = in_array( $estado, self::DELIVERED_STATES, true );
		$closed         = in_array( $estado, self::CLOSED_STATES, true );

		$retraso = false;
		if ( $effective && $effective < $as_of_date && ! $closed ) {
			$retraso = true;
		}

		$a_tiempo = $entregado && ! $retraso;

		return array(
			'committed' => '' !== $effective,
			'prorroga'  => $has_prorroga,
			'retraso'   => $retraso,
			'entregado' => $entregado,
			'a_tiempo'  => $a_tiempo,
		);
	}

	/**
	 * @param array<string, mixed> $previous Semana anterior.
	 * @param array<string, mixed> $current  Semana actual.
	 * @return array<string, int|float>
	 */
	private static function build_delta( $previous, $current ) {
		$keys = array( 'comprometidos', 'prorrogas', 'retrasos', 'entregas_a_tiempo', 'cumplimiento_pct' );

		$delta = array();
		foreach ( $keys as $key ) {
			$delta[ $key ] = (float) ( $current[ $key ] ?? 0 ) - (float) ( $previous[ $key ] ?? 0 );
		}

		return $delta;
	}

	/**
	 * @return array<string, int|float|string>
	 */
	private static function empty_metrics() {
		return array(
			'semana_inicio'      => '',
			'semana_fin'         => '',
			'week_label'         => '',
			'comprometidos'      => 0,
			'prorrogas'          => 0,
			'retrasos'           => 0,
			'entregas_a_tiempo'  => 0,
			'entregados'         => 0,
			'cumplimiento_pct'   => 0.0,
		);
	}

	/**
	 * @param string $week_start Lunes.
	 * @param string $range_end  Límite superior opcional del rango consultado.
	 * @return string
	 */
	private static function week_as_of_date( $week_start, $range_end = '' ) {
		$days     = Logistica_Forming_Planning::week_days( $week_start );
		$week_end = ! empty( $days ) ? (string) ( end( $days )['date'] ?? $week_start ) : $week_start;
		$today    = wp_date( 'Y-m-d', null, new DateTimeZone( Logistica_User_Panel::PANEL_TIMEZONE ) );

		$as_of = $week_end;
		if ( $today < $as_of ) {
			$as_of = $today;
		}
		if ( '' !== $range_end && $range_end < $as_of ) {
			$as_of = $range_end;
		}

		return $as_of;
	}

	/**
	 * @param string $value Fecha.
	 * @return string
	 */
	private static function sanitize_date( $value ) {
		$value = trim( (string) $value );
		if ( '' === $value || '0000-00-00' === $value ) {
			return '';
		}

		$date = DateTime::createFromFormat( 'Y-m-d', $value, new DateTimeZone( 'UTC' ) );
		if ( ! $date || $date->format( 'Y-m-d' ) !== $value ) {
			return '';
		}

		return $value;
	}

	/**
	 * @param string $week_start Inicio.
	 * @param string $week_end   Fin.
	 * @return string
	 */
	private static function format_week_label( $week_start, $week_end ) {
		$start_ts = strtotime( $week_start . ' UTC' );
		$end_ts   = strtotime( $week_end . ' UTC' );
		if ( ! $start_ts || ! $end_ts ) {
			return $week_start;
		}

		return wp_date( 'd/m', $start_ts ) . ' – ' . wp_date( 'd/m', $end_ts );
	}
}
