<?php
/**
 * Scheduled task handler.
 *
 * @package MerchantKits\AttendanceManager
 */

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

/**
 * Registers callbacks for the plugin's WP-Cron events.
 */
class MKAM_Cron {

	/**
	 * Hook the cron callbacks.
	 */
	public function __construct() {
		add_action( 'mkam_auto_clock_out', array( $this, 'auto_clock_out' ) );
		add_action( 'mkam_daily_absent_marker', array( $this, 'mark_absentees' ) );
		add_action( 'mkam_weekly_digest', array( $this, 'send_weekly_digest' ) );
		add_action( 'mkam_leave_year_reset', array( $this, 'maybe_reset_leave_year' ) );
	}

	/**
	 * Auto clock-out open attendance records past shift end + buffer.
	 *
	 * @return void
	 */
	public function auto_clock_out() {
		global $wpdb;

		if ( ! get_option( 'mkam_auto_clock_out', 1 ) ) {
			return;
		}

		$table  = MKAM_Attendance::table();
		$buffer = (int) get_option( 'mkam_auto_clock_out_buffer', 60 );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
		$open = $wpdb->get_results(
			"SELECT * FROM {$table} WHERE clock_in IS NOT NULL AND clock_out IS NULL",
			ARRAY_A
		);

		if ( empty( $open ) ) {
			return;
		}

		$now = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested

		foreach ( $open as $record ) {
			$employee = MKAM_Employee::get( $record['employee_id'] );
			$shift    = ( $employee && $employee['shift_id'] ) ? MKAM_Shift::get( $employee['shift_id'] ) : array();

			$clock_out_ts = $this->resolve_auto_clock_out_time( $record, $shift, $buffer );

			// Only auto clock-out once the threshold has passed.
			if ( $now < $clock_out_ts ) {
				continue;
			}

			$clock_out = gmdate( 'Y-m-d H:i:s', $clock_out_ts );
			$hours     = MKAM_Attendance::calculate_hours( $record['clock_in'], $clock_out, (array) $shift );

			MKAM_Attendance::update(
				$record['id'],
				array(
					'clock_out'       => $clock_out,
					'total_hours'     => $hours['total_hours'],
					'overtime_hours'  => $hours['overtime_hours'],
					'is_manual_entry' => 1,
					'clock_out_note'  => __( 'Auto clock-out by system', 'merchantkits-attendance-manager' ),
				)
			);

			do_action( 'mkam_clock_out', (int) $record['id'], (int) $record['employee_id'] );
		}
	}

	/**
	 * Determine the auto clock-out timestamp for a record.
	 *
	 * @param array $record Attendance row.
	 * @param array $shift  Shift row (may be empty).
	 * @param int   $buffer Buffer minutes after shift end.
	 * @return int Unix timestamp.
	 */
	private function resolve_auto_clock_out_time( $record, $shift, $buffer ) {
		$date = $record['date'];

		if ( ! empty( $shift['end_time'] ) ) {
			$end = strtotime( $date . ' ' . $shift['end_time'] );

			if ( ! empty( $shift['is_overnight'] ) ) {
				$end += DAY_IN_SECONDS;
			}

			return $end + ( $buffer * MINUTE_IN_SECONDS );
		}

		// No shift: fall back to clock-in + 8 hours + buffer.
		return strtotime( $record['clock_in'] ) + ( 8 * HOUR_IN_SECONDS ) + ( $buffer * MINUTE_IN_SECONDS );
	}

	/**
	 * Mark active employees absent for yesterday when no record/leave exists.
	 *
	 * @return void
	 */
	public function mark_absentees() {
		$yesterday = gmdate( 'Y-m-d', strtotime( '-1 day', current_time( 'timestamp' ) ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
		$weekday   = (int) gmdate( 'N', strtotime( $yesterday ) );

		$employees = MKAM_Employee::query( array( 'status' => 'active' ) );

		foreach ( $employees as $employee ) {
			// Skip if a record already exists for that date.
			if ( MKAM_Attendance::get_for_date( $employee['id'], $yesterday ) ) {
				continue;
			}

			$shift        = $employee['shift_id'] ? MKAM_Shift::get( $employee['shift_id'] ) : null;
			$status       = 'absent';
			$working_days = $shift ? array_map( 'intval', explode( ',', $shift['working_days'] ) ) : array( 1, 2, 3, 4, 5 );

			if ( ! in_array( $weekday, $working_days, true ) ) {
				$status = 'weekend';
			}

			// Approved leave overrides absent.
			if ( $this->has_approved_leave( $employee['id'], $yesterday ) ) {
				$status = 'on_leave';
			}

			MKAM_Attendance::create(
				array(
					'employee_id' => $employee['id'],
					'date'        => $yesterday,
					'status'      => $status,
				)
			);
		}

		do_action( 'mkam_daily_cron' );
	}

	/**
	 * Check whether an employee has approved leave covering a date.
	 *
	 * @param int    $employee_id Employee ID.
	 * @param string $date        Y-m-d.
	 * @return bool
	 */
	private function has_approved_leave( $employee_id, $date ) {
		global $wpdb;
		$table = MKAM_Leave::table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$count = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(*) FROM {$table} WHERE employee_id = %d AND status = 'approved' AND start_date <= %s AND end_date >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				absint( $employee_id ),
				$date,
				$date
			)
		);

		return $count > 0;
	}

	/**
	 * Compile last week's attendance and email a digest to the admin.
	 *
	 * @return void
	 */
	public function send_weekly_digest() {
		if ( ! get_option( 'mkam_enable_weekly_digest', 0 ) ) {
			return;
		}

		$recipient = get_option( 'mkam_admin_email', get_option( 'admin_email' ) );
		$date_from = gmdate( 'Y-m-d', strtotime( '-7 days', current_time( 'timestamp' ) ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
		$date_to   = current_time( 'Y-m-d' );

		$records = MKAM_Attendance::query(
			array(
				'date_from' => $date_from,
				'date_to'   => $date_to,
			)
		);

		$summary = '';

		if ( class_exists( 'MKAM_AI_Insights' ) ) {
			$ai      = new MKAM_AI_Insights();
			$summary = $ai->generate_digest_summary( $records, $date_from, $date_to );
			if ( is_wp_error( $summary ) ) {
				$summary = '';
			}
		}

		if ( class_exists( 'MKAM_Email' ) ) {
			$email = new MKAM_Email();
			$email->send(
				'attendance_summary',
				$recipient,
				array(
					'date_from' => $date_from,
					'date_to'   => $date_to,
					'records'   => $records,
					'summary'   => $summary,
				)
			);
		}

		do_action( 'mkam_weekly_cron' );
	}

	/**
	 * Reset the leave year on the configured financial year start.
	 *
	 * Runs daily but only acts on the first day of the financial year.
	 *
	 * @return void
	 */
	public function maybe_reset_leave_year() {
		$start_month = get_option( 'mkam_financial_year_start', 'January' );
		$today       = current_time( 'Y-m-d' );

		$month_name = gmdate( 'F', strtotime( $today ) );
		$day_of_month = (int) gmdate( 'j', strtotime( $today ) );

		if ( $month_name !== $start_month || 1 !== $day_of_month ) {
			return;
		}

		do_action( 'mkam_leave_year_reset_run', $today );
	}
}
