<?php
/**
 * Dashboard Service
 */

namespace RadiusTheme\RadiusBooking\Services;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}
use RadiusTheme\RadiusBooking\Repositories\AppointmentRepository;
use RadiusTheme\RadiusBooking\Repositories\DashboardRepository;

/**
 * Services/DashboardService.php
 * Business logic layer for Dashboard.
 */
class DashboardService {

	/**
	 * Appointment Repository
	 *
	 * @var AppointmentRepository $appointRepo
	 */
	private AppointmentRepository $appointRepo;

	/**
	 * Dashboard Repository
	 *
	 * @var DashboardRepository $dashboardRepo
	 */
	private DashboardRepository $dashboardRepo;


	/**
	 * Constructor method for initializing repositories.
	 *
	 * @param AppointmentRepository $appointRepo Repository for managing appointments.
	 * @param DashboardRepository $dashboardRepo Repository for managing dashboard data.
	 *
	 * @return void
	 */
	public function __construct(
		AppointmentRepository $appointRepo,
		DashboardRepository $dashboardRepo,
	) {
		$this->appointRepo   = $appointRepo;
		$this->dashboardRepo = $dashboardRepo;
	}

	/**
	 * Retrieves a list of recent activities, including updates or creations of appointments, customers, payments, employees, and services.
	 *
	 * @return array An array of recent activities, where each activity includes information such as ID, type, descriptive message, and timestamp.
	 */
	public function getRecentActivities(): array {
		$recentActivities = array();

		$appointment = $this->dashboardRepo->latestAppointment();
		$customer    = $this->dashboardRepo->latestCustomer();
		$payment     = $this->dashboardRepo->latestPayment();
		$employee    = $this->dashboardRepo->latestEmployee();
		$service     = $this->dashboardRepo->latestService();

		if ( $appointment ) {
			$recentActivities[] = array(
				'id'      => $appointment->id,
				'type'    => 'appointment',
				'label'    => __( 'New appointment', 'radius-booking' ),
				'message' => $service?->name,
				'time'    => $this->timeAgo( $appointment->created_at ),
			);
		}

		if ( $customer ) {
			$recentActivities[] = array(
				'id'      => $customer->id,
				'type'    => 'customer',
				'label' => __( 'Registered', 'radius-booking' ),
				'message' => 'New customer registered',
				'time'    => $this->timeAgo( $customer->created_at ),
			);
		}

		if ( $payment ) {
			$recentActivities[] = array(
				'id'      => $payment->id,
				'type'    => 'payment',
				'label' => __( 'Payment', 'radius-booking' ),
				'message' => sprintf(
					'Payment %s: $%s - Booking #%s',
					str_replace( '_', ' ', $payment->status ),
					$payment->amount,
					$payment->booking_id
				),
				'time'    => $this->timeAgo( $payment->updated_at ),
			);
		}

		if ( $employee ) {
			$recentActivities[] = array(
				'id'      => $employee->id,
				'type'    => 'employee',
				'label' => __( 'Staff updated', 'radius-booking' ),
				'message' => $employee->email,
				'time'    => $this->timeAgo( $employee->updated_at ),
			);
		}

		if ( $service ) {
			$recentActivities[] = array(
				'id'      => $service->id,
				'type'    => 'service',
				'label' => __( 'Update', 'radius-booking' ),
				'message' => 'New/Updated service: ' . $service->name,
				'time'    => $this->timeAgo( $service->updated_at ),
			);
		}

		return $recentActivities;
	}

	/**
	 * Calculates the time difference between the provided datetime and the current time,
	 * and returns a human-readable string representing this difference.
	 *
	 * @param string $datetime A datetime string to calculate the time difference from.
	 *
	 * @return string A string indicating the time difference, in units of seconds, minutes, hours, or days.
	 */
	private function timeAgo( $datetime ): string {
		$timestamp = strtotime( $datetime );
		$diff      = time() - $timestamp;

		if ( $diff < 60 ) {
			return $diff . ' seconds ago';
		} elseif ( $diff < 3600 ) {
			return floor( $diff / 60 ) . ' minutes ago';
		} elseif ( $diff < 86400 ) {
			return floor( $diff / 3600 ) . ' hours ago';
		} else {
			return floor( $diff / 86400 ) . ' days ago';
		}
	}


	/**
	 * Retrieves statistics based on the specified view.
	 *
	 * @param string $view The view type for statistics. Acceptable values: 'yearly', 'month', 'week'.
	 *
	 * @return array The statistics data including labels and associated data points.
	 */
	public function getStatistics( string $view ): array {
		switch ( $view ) {
			case 'yearly':
				return $this->getYearlyStatistics();
			case 'month':
				return $this->getMonthlyStatistics();
			case 'week':
				return $this->getWeeklyStatistics();
			default:
				return array(
					'labels' => array(),
					'data'   => array(),
				);
		}
	}

	/**
	 * Yearly statistics
	 *
	 * @return array An associative array containing 'labels' for months of the year and 'data' for corresponding statistics.
	 */
	private function getYearlyStatistics(): array {
		$year    = (int) gmdate( 'Y' );
		$results = $this->dashboardRepo->getYearlyStatistics( $year );

		$labels = array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
		$data   = array_fill( 0, 12, 0 );

		foreach ( $results as $row ) {
			$data[ intval( $row->month ) - 1 ] = intval( $row->total );
		}

		return compact( 'labels', 'data' );
	}

	/**
	 * Retrieves monthly statistics data, including labels for each day of the month
	 * and corresponding data values based on the repository's results.
	 *
	 * @return array An associative array containing 'labels' (an array of days in the month)
	 *               and 'data' (an array of corresponding values for each day).
	 */
	private function getMonthlyStatistics(): array {
		$year  = (int) gmdate( 'Y' );
		$month = (int) gmdate( 'm' );

		$results = $this->dashboardRepo->getMonthlyStatistics( $year, $month );

		$daysInMonth = cal_days_in_month( CAL_GREGORIAN, $month, $year );
		$labels      = range( 1, $daysInMonth );
		$data        = array_fill( 0, $daysInMonth, 0 );

		foreach ( $results as $row ) {
			$data[ intval( $row->day ) - 1 ] = intval( $row->total );
		}

		return compact( 'labels', 'data' );
	}

	/**
	 * Retrieves the weekly statistics for the current week, including data and labels for each day from Monday to Sunday.
	 *
	 * @return array An associative array containing 'labels' (days of the week) and 'data' (statistics for each day).
	 */
	private function getWeeklyStatistics(): array {
		$monday = gmdate( 'Y-m-d', strtotime( 'monday this week' ) );
		$sunday = gmdate( 'Y-m-d', strtotime( 'sunday this week' ) );

		$results = $this->dashboardRepo->getWeeklyStatistics( $monday, $sunday );

		$labels = array( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' );
		$data   = array_fill( 0, 7, 0 );

		foreach ( $results as $row ) {
			$data[ intval( $row->weekday ) - 1 ] = intval( $row->total );
		}

		return compact( 'labels', 'data' );
	}

	/**
	 * Retrieves revenue data based on the specified view type.
	 *
	 * @param string $view The view type for revenue data ('yearly', 'month', 'week').
	 *
	 * @return array An associative array containing labels and data corresponding to the revenue view type. Defaults to empty labels and data if the view type is invalid.
	 */
	public function getRevenue( string $view ): array {
		return match ( $view ) {
			'yearly' => $this->getYearlyRevenue(),
			'month' => $this->getMonthlyRevenue(),
			'week' => $this->getWeeklyRevenue(),
			default => array(
				'labels' => array(),
				'data'   => array(),
				'current_total' => ''
			),
		};
	}

	/**
	 * Retrieves the yearly revenue data for the current year, including monthly labels and revenue statistics for each month.
	 *
	 * @return array An associative array containing 'labels' (month names) and 'data' (revenue for each month).
	 */
	private function getYearlyRevenue(): array {
		$year        = (int) gmdate( 'Y' );
		$last_year   = $year - 1;

		$results     = $this->dashboardRepo->getYearlyRevenue( $year );
		$lastResults = $this->dashboardRepo->getYearlyRevenue( $last_year );

		$labels = array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
		$data   = array_fill( 0, 12, 0 );
		$last   = array_fill( 0, 12, 0 );

		foreach ( $results as $row ) {
			$data[ intval( $row->month ) - 1 ] = (float) $row->total;
		}

		foreach ( $lastResults as $row ) {
			$last[ intval( $row->month ) - 1 ] = (float) $row->total;
		}

		$current_total      = array_sum( $data );
		$last_total_amount = array_sum( $last );

		$percentage_change = $this->calculatePercentageChange( $current_total, $last_total_amount );

		return compact( 'labels', 'data', 'current_total', 'percentage_change' );
	}


	/**
	 * Retrieves the revenue data for the current month, including daily revenue totals and corresponding day labels.
	 *
	 * @return array An associative array containing 'labels' (days of the month) and 'data' (revenue totals for each day).
	 */
	private function getMonthlyRevenue(): array {
		$year      = (int) gmdate( 'Y' );
		$month     = (int) gmdate( 'm' );

		$prevMonth = $month - 1;
		$prevYear  = $year;

		if ( $prevMonth === 0 ) {
			$prevMonth = 12;
			$prevYear--;
		}

		$results     = $this->dashboardRepo->getMonthlyRevenue( $year, $month );
		$lastResults = $this->dashboardRepo->getMonthlyRevenue( $prevYear, $prevMonth );

		$daysInMonth = cal_days_in_month( CAL_GREGORIAN, $month, $year );
		$labels      = range( 1, $daysInMonth );
		$data        = array_fill( 0, $daysInMonth, 0 );

		foreach ( $results as $row ) {
			$data[ intval( $row->day ) - 1 ] = (float) $row->total;
		}

		$current_total = array_sum( $data );
		$last_total    = array_sum( wp_list_pluck( $lastResults, 'total' ) );

		$percentage_change = $this->calculatePercentageChange( $current_total, $last_total );

		return compact( 'labels', 'data', 'current_total', 'percentage_change' );
	}


	/**
	 * Calculates the weekly revenue for the current week, including revenue data and labels for each day from Monday to Sunday.
	 *
	 * @return array An associative array containing 'labels' (days of the week) and 'data' (revenue values for each day).
	 */
	private function getWeeklyRevenue(): array {
		$monday = gmdate( 'Y-m-d', strtotime( 'monday this week' ) );
		$sunday = gmdate( 'Y-m-d', strtotime( 'sunday this week' ) );


		$lastMonday = gmdate( 'Y-m-d', strtotime( 'monday last week' ) );
		$lastSunday = gmdate( 'Y-m-d', strtotime( 'sunday last week' ) );

		$results = $this->dashboardRepo->getWeeklyRevenue( $monday, $sunday );
		$lastResults = $this->dashboardRepo->getWeeklyRevenue( $lastMonday, $lastSunday );

		$labels = array( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' );
		$data   = array_fill( 0, 7, 0 );

		foreach ( $results as $row ) {
			$data[ intval( $row->weekday ) - 1 ] = (float) $row->total;
		}

		$current_total = array_sum( $data );
		$last_total    = array_sum( wp_list_pluck( $lastResults, 'total' ) );
		$percentage_change = $this->calculatePercentageChange( $current_total, $last_total );

		return compact( 'labels', 'data', 'current_total', 'percentage_change' );

	}

	/**
	 * Calculate percentage change between current and previous value.
	 */
	private function calculatePercentageChange( float $current, float $previous ): float {
		if ( $previous == 0 ) {
			return $current > 0 ? 100 : 0;
		}

		return round( ( ( $current - $previous ) / $previous ) * 100, 2 );
	}
}
