<?php
/**
 * Métricas agregadas de planeación (formado / impresión).
 *
 * @package Logistica
 */

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

/**
 * Calcula capacidad, ocupación, eficiencia y retrasos desde schedules guardados.
 */
class Logistica_Planning_Metrics {

	const LUNCH_START = 720;
	const LUNCH_END   = 780;

	/**
	 * Resumen mensual por semanas para una máquina.
	 *
	 * @param class-string $planning_class Logistica_Forming_Planning o Logistica_Print_Planning.
	 * @param int          $year           Año.
	 * @param int          $month          Mes 1-12.
	 * @param string       $machine        Slug máquina.
	 * @return array<string, mixed>
	 */
	public static function get_month_summary( $planning_class, $year, $month, $machine ) {
		global $wpdb;

		$year  = max( 2000, min( 2100, (int) $year ) );
		$month = max( 1, min( 12, (int) $month ) );

		$month_start = sprintf( '%04d-%02d-01', $year, $month );
		$month_end   = gmdate( 'Y-m-t', strtotime( $month_start . ' UTC' ) );

		$range_start = $planning_class::week_start( gmdate( 'Y-m-d', strtotime( $month_start . ' -6 days UTC' ) ) );
		$range_end   = $planning_class::week_start( $month_end );

		$table = $planning_class::table_name();
		$rows  = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT semana_inicio, day_hours, include_lunch, lunch_start, lunch_end, tasks, jornada_inicio, jornada_fin
				FROM {$table}
				WHERE maquina = %s AND semana_inicio >= %s AND semana_inicio <= %s
				ORDER BY semana_inicio ASC",
				$planning_class::sanitize_machine( $machine ),
				$range_start,
				$range_end
			),
			ARRAY_A
		);

		$weeks  = array();
		$totals = self::empty_totals();

		if ( is_array( $rows ) ) {
			foreach ( $rows as $row ) {
				$week_start = (string) ( $row['semana_inicio'] ?? '' );
				if ( '' === $week_start ) {
					continue;
				}

				$schedule = self::row_to_schedule( $row, $week_start, $planning_class );
				$metrics  = self::aggregate_schedule( $schedule, $week_start, $planning_class );
				$weeks[]  = $metrics;
				self::merge_totals( $totals, $metrics );
			}
		}

		if ( $totals['available_minutes'] > 0 ) {
			$totals['utilization_pct'] = round( ( $totals['occupied_minutes'] / $totals['available_minutes'] ) * 100, 1 );
		}
		if ( $totals['occupied_minutes'] > 0 ) {
			$totals['efficiency_pct'] = round( ( $totals['done_minutes'] / $totals['occupied_minutes'] ) * 100, 1 );
		}

		return array(
			'year'        => $year,
			'month'       => $month,
			'month_label' => wp_date( 'F Y', strtotime( $month_start . ' UTC' ) ),
			'machine'     => $planning_class::sanitize_machine( $machine ),
			'weeks'       => $weeks,
			'totals'      => $totals,
		);
	}

	/**
	 * Comparación semana anterior vs semana actual (programación guardada).
	 *
	 * @param class-string $planning_class     Logistica_Forming_Planning o Logistica_Print_Planning.
	 * @param string       $current_week_start Lunes Y-m-d de la semana visible.
	 * @param string       $machine            Slug máquina.
	 * @return array<string, mixed>
	 */
	public static function get_week_comparison_summary( $planning_class, $current_week_start, $machine ) {
		$machine            = $planning_class::sanitize_machine( $machine );
		$current_week_start = $planning_class::week_start( $current_week_start );
		$previous_week_start = $planning_class::week_start(
			gmdate( 'Y-m-d', strtotime( $current_week_start . ' -7 days UTC' ) )
		);

		$weeks         = array();
		$has_any_saved = false;

		foreach ( array( $previous_week_start, $current_week_start ) as $week_start ) {
			$schedule = $planning_class::get_schedule( $week_start, $machine );
			$saved    = ! empty( $schedule['id'] );
			if ( $saved ) {
				$has_any_saved = true;
			}

			$payload = array(
				'day_hours'     => $schedule['day_hours'] ?? array(),
				'include_lunch' => $schedule['include_lunch'] ?? 1,
				'lunch_start'   => $schedule['lunch_start'] ?? self::LUNCH_START,
				'lunch_end'     => $schedule['lunch_end'] ?? self::LUNCH_END,
				'tasks'         => $saved ? ( $schedule['tasks'] ?? array() ) : array(),
			);

			$metrics               = self::aggregate_schedule( $payload, $week_start, $planning_class );
			$metrics['has_saved']  = $saved;
			$metrics['week_role']  = $week_start === $current_week_start ? 'current' : 'previous';
			$weeks[]               = $metrics;
		}

		$prev_label = (string) ( $weeks[0]['week_label'] ?? '' );
		$curr_label = (string) ( $weeks[1]['week_label'] ?? '' );

		return array(
			'current_week'     => $current_week_start,
			'previous_week'    => $previous_week_start,
			'comparison_label' => trim( $prev_label . ' vs ' . $curr_label ),
			'has_saved_weeks'  => $has_any_saved,
			'machine'          => $machine,
			'weeks'            => $weeks,
		);
	}

	/**
	 * Resumen por semanas dentro de un rango (gráficos por período).
	 *
	 * @param class-string $planning_class Logistica_Forming_Planning o Logistica_Print_Planning.
	 * @param string       $date_from      Y-m-d.
	 * @param string       $date_to        Y-m-d.
	 * @param string       $machine        Slug máquina.
	 * @return array<string, mixed>|\WP_Error
	 */
	public static function get_period_summary( $planning_class, $date_from, $date_to, $machine ) {
		$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' ) );
		}

		global $wpdb;

		$machine    = $planning_class::sanitize_machine( $machine );
		$week_start = $planning_class::week_start( $date_from );
		$week_end   = $planning_class::week_start( $date_to );
		$table      = $planning_class::table_name();

		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT semana_inicio, day_hours, include_lunch, lunch_start, lunch_end, tasks, jornada_inicio, jornada_fin
				FROM {$table}
				WHERE maquina = %s AND semana_inicio >= %s AND semana_inicio <= %s
				ORDER BY semana_inicio ASC",
				$machine,
				$week_start,
				$week_end
			),
			ARRAY_A
		);

		$rows_by_week = array();
		if ( is_array( $rows ) ) {
			foreach ( $rows as $row ) {
				$key = (string) ( $row['semana_inicio'] ?? '' );
				if ( '' !== $key ) {
					$rows_by_week[ $key ] = $row;
				}
			}
		}

		$weeks         = array();
		$has_any_saved = false;
		$cursor        = $week_start;

		while ( $cursor <= $week_end ) {
			if ( isset( $rows_by_week[ $cursor ] ) ) {
				$schedule = self::row_to_schedule( $rows_by_week[ $cursor ], $cursor, $planning_class );
				$metrics  = self::aggregate_schedule( $schedule, $cursor, $planning_class );
				$metrics['has_saved'] = true;
				$has_any_saved        = true;
			} else {
				$metrics = self::empty_week_metrics( $cursor, $planning_class );
			}

			$weeks[] = $metrics;
			$cursor  = gmdate( 'Y-m-d', strtotime( $cursor . ' +7 days UTC' ) );
		}

		return array(
			'date_from'         => $date_from,
			'date_to'           => $date_to,
			'comparison_label'  => self::format_period_label( $date_from, $date_to ),
			'has_saved_weeks'   => $has_any_saved,
			'period_mode'       => true,
			'machine'           => $machine,
			'weeks'             => $weeks,
		);
	}

	/**
	 * Métricas vacías para una semana sin programación guardada.
	 *
	 * @param string       $week_start     Lunes Y-m-d.
	 * @param class-string $planning_class Clase planning.
	 * @return array<string, mixed>
	 */
	private static function empty_week_metrics( $week_start, $planning_class ) {
		$week_days = $planning_class::week_days( $week_start );
		$week_end  = ! empty( $week_days ) ? (string) ( end( $week_days )['date'] ?? $week_start ) : $week_start;

		return array(
			'semana_inicio'     => $week_start,
			'semana_fin'        => $week_end,
			'week_label'        => self::format_week_label( $week_start, $week_end ),
			'working_days'      => 0,
			'available_minutes' => 0,
			'occupied_minutes'  => 0,
			'free_minutes'      => 0,
			'utilization_pct'   => 0.0,
			'efficiency_pct'    => 0.0,
			'delay_minutes'     => 0,
			'done_minutes'      => 0,
			'task_count'        => 0,
			'by_status'         => array(
				'pending'     => array( 'count' => 0, 'minutes' => 0 ),
				'in_progress' => array( 'count' => 0, 'minutes' => 0 ),
				'done'        => array( 'count' => 0, 'minutes' => 0 ),
				'delayed'     => array( 'count' => 0, 'minutes' => 0 ),
			),
			'has_saved'         => false,
		);
	}

	/**
	 * @param array<string, mixed> $row            Fila BD.
	 * @param string               $week_start     Lunes Y-m-d.
	 * @param class-string         $planning_class Clase planning.
	 * @return array<string, mixed>
	 */
	private static function row_to_schedule( $row, $week_start, $planning_class ) {
		$tasks = json_decode( (string) ( $row['tasks'] ?? '[]' ), true );
		if ( ! is_array( $tasks ) ) {
			$tasks = array();
		}

		$legacy_start = (int) ( $row['jornada_inicio'] ?? 420 );
		$legacy_end   = (int) ( $row['jornada_fin'] ?? 1020 );
		$day_hours    = $planning_class::sanitize_day_hours(
			json_decode( (string) ( $row['day_hours'] ?? '' ), true ),
			$legacy_start,
			$legacy_end,
			$week_start
		);

		return array(
			'day_hours'     => $day_hours,
			'include_lunch' => (int) ( $row['include_lunch'] ?? 1 ),
			'lunch_start'   => (int) ( $row['lunch_start'] ?? self::LUNCH_START ),
			'lunch_end'     => (int) ( $row['lunch_end'] ?? self::LUNCH_END ),
			'tasks'         => $planning_class::sanitize_tasks( $tasks ),
		);
	}

	/**
	 * @param array<string, mixed> $schedule     Schedule normalizado.
	 * @param string               $week_start   Lunes Y-m-d.
	 * @param class-string         $planning_class Clase planning.
	 * @return array<string, mixed>
	 */
	public static function aggregate_schedule( $schedule, $week_start, $planning_class ) {
		$day_hours     = $planning_class::apply_automatic_day_types(
			$schedule['day_hours'] ?? $planning_class::default_day_hours(),
			$week_start
		);
		$include_lunch = ! empty( $schedule['include_lunch'] );
		$lunch         = $planning_class::sanitize_lunch_hours(
			$schedule['lunch_start'] ?? null,
			$schedule['lunch_end'] ?? null
		);
		$lunch_start   = $lunch['lunch_start'];
		$lunch_end     = $lunch['lunch_end'];
		$tasks         = is_array( $schedule['tasks'] ?? null ) ? $schedule['tasks'] : array();

		$available_minutes = 0;
		$working_days      = 0;

		for ( $day = 0; $day <= 6; $day++ ) {
			$key  = (string) $day;
			$slot = isset( $day_hours[ $key ] ) && is_array( $day_hours[ $key ] ) ? $day_hours[ $key ] : array();
			if ( $planning_class::is_non_working_day_type( $slot['day_type'] ?? '' ) ) {
				continue;
			}

			$start = isset( $slot['start'] ) ? (int) $slot['start'] : 420;
			$end   = isset( $slot['end'] ) ? (int) $slot['end'] : 1020;
			if ( $end <= $start ) {
				continue;
			}

			$capacity = $end - $start;
			if ( $include_lunch && $start < $lunch_end && $end > $lunch_start ) {
				$capacity -= max( 0, min( $lunch_end, $end ) - max( $lunch_start, $start ) );
			}

			$available_minutes += max( 0, $capacity );
			++$working_days;
		}

		$by_status = array(
			'pending'     => array( 'count' => 0, 'minutes' => 0 ),
			'in_progress' => array( 'count' => 0, 'minutes' => 0 ),
			'done'        => array( 'count' => 0, 'minutes' => 0 ),
			'delayed'     => array( 'count' => 0, 'minutes' => 0 ),
		);

		$occupied_minutes = 0;
		$delay_minutes    = 0;
		$done_minutes     = 0;

		foreach ( $tasks as $task ) {
			if ( ! is_array( $task ) ) {
				continue;
			}

			$minutes = max( 0, (int) ( $task['duration_minutes'] ?? 0 ) );
			$status  = sanitize_key( (string) ( $task['status'] ?? 'pending' ) );
			if ( ! isset( $by_status[ $status ] ) ) {
				$status = 'pending';
			}

			$occupied_minutes += $minutes;
			++$by_status[ $status ]['count'];
			$by_status[ $status ]['minutes'] += $minutes;

			if ( 'done' === $status ) {
				$done_minutes += $minutes;
			}

			if ( 'delayed' === $status || ! empty( $task['reschedule_delay'] ) ) {
				$delay_minutes += $minutes;
			}
		}

		$free_minutes    = max( 0, $available_minutes - $occupied_minutes );
		$utilization_pct = $available_minutes > 0 ? round( ( $occupied_minutes / $available_minutes ) * 100, 1 ) : 0.0;
		$efficiency_pct  = $occupied_minutes > 0 ? round( ( $done_minutes / $occupied_minutes ) * 100, 1 ) : 0.0;

		$week_days = $planning_class::week_days( $week_start );
		$week_end  = ! empty( $week_days ) ? end( $week_days )['date'] ?? $week_start : $week_start;

		return array(
			'semana_inicio'      => $week_start,
			'semana_fin'         => (string) $week_end,
			'week_label'         => self::format_week_label( $week_start, (string) $week_end ),
			'working_days'       => $working_days,
			'available_minutes'  => $available_minutes,
			'occupied_minutes'   => $occupied_minutes,
			'free_minutes'       => $free_minutes,
			'utilization_pct'    => $utilization_pct,
			'efficiency_pct'     => $efficiency_pct,
			'delay_minutes'      => $delay_minutes,
			'done_minutes'       => $done_minutes,
			'task_count'         => count( $tasks ),
			'by_status'          => $by_status,
		);
	}

	/**
	 * @return array<string, int|float>
	 */
	private static function empty_totals() {
		return array(
			'available_minutes' => 0,
			'occupied_minutes'  => 0,
			'free_minutes'      => 0,
			'delay_minutes'     => 0,
			'done_minutes'      => 0,
			'task_count'        => 0,
			'utilization_pct'   => 0.0,
			'efficiency_pct'    => 0.0,
			'by_status'         => array(
				'pending'     => array( 'count' => 0, 'minutes' => 0 ),
				'in_progress' => array( 'count' => 0, 'minutes' => 0 ),
				'done'        => array( 'count' => 0, 'minutes' => 0 ),
				'delayed'     => array( 'count' => 0, 'minutes' => 0 ),
			),
		);
	}

	/**
	 * @param array<string, mixed> $totals Acumulador.
	 * @param array<string, mixed> $week   Métricas semana.
	 * @return void
	 */
	private static function merge_totals( &$totals, $week ) {
		$totals['available_minutes'] += (int) ( $week['available_minutes'] ?? 0 );
		$totals['occupied_minutes']  += (int) ( $week['occupied_minutes'] ?? 0 );
		$totals['free_minutes']      += (int) ( $week['free_minutes'] ?? 0 );
		$totals['delay_minutes']     += (int) ( $week['delay_minutes'] ?? 0 );
		$totals['done_minutes']      += (int) ( $week['done_minutes'] ?? 0 );
		$totals['task_count']        += (int) ( $week['task_count'] ?? 0 );

		foreach ( array( 'pending', 'in_progress', 'done', 'delayed' ) as $status ) {
			$totals['by_status'][ $status ]['count']   += (int) ( $week['by_status'][ $status ]['count'] ?? 0 );
			$totals['by_status'][ $status ]['minutes'] += (int) ( $week['by_status'][ $status ]['minutes'] ?? 0 );
		}
	}

	/**
	 * @param string $week_start Inicio semana.
	 * @param string $week_end   Fin semana.
	 * @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 );
	}

	/**
	 * @param string $date_from Y-m-d.
	 * @param string $date_to   Y-m-d.
	 * @return string
	 */
	private static function format_period_label( $date_from, $date_to ) {
		$start_ts = strtotime( $date_from . ' UTC' );
		$end_ts   = strtotime( $date_to . ' UTC' );
		if ( ! $start_ts || ! $end_ts ) {
			return '';
		}

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

	/**
	 * @param string $value Fecha Y-m-d.
	 * @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;
	}
}
