<?php
/**
 * REST endpoint for dashboard data.
 *
 * @since 1.0.0
 */

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

class ZINV_REST_Dashboard {

	private string $namespace;

	public function __construct( string $namespace ) {
		$this->namespace = $namespace;
	}

	public function register(): void {
		register_rest_route( $this->namespace, '/dashboard', [
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => [ $this, 'get_dashboard' ],
			'permission_callback' => function () {
				return current_user_can( 'zinv_view_dashboard' );
			},
		] );
	}

	public function get_dashboard( WP_REST_Request $request ): WP_REST_Response {
		$summary  = new ZINV_Summary();
		$activity = new ZINV_Activity();

		$kpis = get_transient( 'zinv_dashboard_kpis' );
		if ( false === $kpis ) {
			$summary->refresh_all();
			$kpis = $summary->get_all();
			set_transient( 'zinv_dashboard_kpis', $kpis, 5 * MINUTE_IN_SECONDS );
		}

		$paid_this_month  = (float) ( $kpis['paid_this_month'] ?? 0 );
		$paid_last_month  = (float) ( $kpis['paid_last_month'] ?? 0 );
		$clients_new      = (int) ( $kpis['clients_new_30d'] ?? 0 );
		$clients_new_prev = (int) ( $kpis['clients_new_prev_30d'] ?? 0 );

		$data = [
			'kpis' => [
				'total_revenue'  => (float) ( $kpis['total_revenue'] ?? 0 ),
				'active_clients' => (int) ( $kpis['active_clients'] ?? 0 ),
				'pending_count'  => (int) ( $kpis['pending_count'] ?? 0 ),
				'pending_amount' => (float) ( $kpis['pending_amount'] ?? 0 ),
				'overdue_count'  => (int) ( $kpis['overdue_count'] ?? 0 ),
				'paid_this_month' => $paid_this_month,
				'estimates_sent'    => (int) ( $kpis['estimates_sent'] ?? 0 ),
				'estimates_approved' => (int) ( $kpis['estimates_approved'] ?? 0 ),
			],
			// Real period-over-period trends. null = no prior-period baseline
			// (e.g. first month of use) — the UI then shows no trend badge.
			'trends' => [
				'revenue_mom'     => self::percent_change( $paid_this_month, $paid_last_month ),
				'clients_new_mom' => self::percent_change( (float) $clients_new, (float) $clients_new_prev ),
				'new_clients_30d' => $clients_new,
			],
			'activity' => $activity->get_recent( 20 ),
			'pending_invoices' => $this->get_pending_invoices(),
			'monthly_revenue'  => $this->get_monthly_revenue(),
		];

		return new WP_REST_Response( $data, 200 );
	}

	/**
	 * Monthly revenue series for the dashboard chart.
	 *
	 * Returns 12 entries (current year Jan..Dec), each with the current-year
	 * and previous-year revenue total. Pre-filled with zeros so months that
	 * have no payments still appear as a bar baseline rather than collapsing
	 * the X axis.
	 *
	 * @return array<int,array{month:string,current:float,previous:float}>
	 */
	private function get_monthly_revenue(): array {
		$cached = get_transient( 'zinv_dash_monthly_revenue' );
		if ( false !== $cached && is_array( $cached ) ) {
			return $cached;
		}

		global $wpdb;

		$pay_table     = ( new ZINV_Database( 'zinv_payments' ) )->get_table_name();
		$current_year  = (int) gmdate( 'Y' );
		$previous_year = $current_year - 1;
		$months        = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];

		// Scaffold zeros for both years so missing months still render.
		$series = [];
		foreach ( $months as $i => $label ) {
			$series[ $i + 1 ] = [
				'month'    => $label,
				'current'  => 0.0,
				'previous' => 0.0,
			];
		}

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT YEAR(created_at) AS y, MONTH(created_at) AS m, COALESCE(SUM(amount), 0) AS revenue
			 FROM {$pay_table}
			 WHERE status = 'successful'
			   AND YEAR(created_at) IN (%d, %d)
			 GROUP BY y, m",
			$current_year,
			$previous_year
		) );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		foreach ( (array) $rows as $row ) {
			$m = (int) $row->m;
			if ( ! isset( $series[ $m ] ) ) {
				continue;
			}
			if ( (int) $row->y === $current_year ) {
				$series[ $m ]['current'] = round( (float) $row->revenue, 2 );
			} elseif ( (int) $row->y === $previous_year ) {
				$series[ $m ]['previous'] = round( (float) $row->revenue, 2 );
			}
		}

		$result = array_values( $series );
		set_transient( 'zinv_dash_monthly_revenue', $result, 5 * MINUTE_IN_SECONDS );
		return $result;
	}

	/**
	 * Percent change from $previous to $current, rounded to one decimal.
	 * Returns null when there is no prior-period baseline (previous is zero),
	 * so the caller can omit the trend rather than show a misleading figure.
	 */
	private static function percent_change( float $current, float $previous ): ?float {
		if ( $previous <= 0.0 ) {
			return null;
		}
		return round( ( ( $current - $previous ) / $previous ) * 100, 1 );
	}

	private function get_pending_invoices(): array {
		$cached = get_transient( 'zinv_dash_pending_invoices' );
		if ( false !== $cached && is_array( $cached ) ) {
			return $cached;
		}

		global $wpdb;
		$inv_table    = ( new ZINV_Database( 'zinv_invoices' ) )->get_table_name();
		$client_table = ( new ZINV_Database( 'zinv_clients' ) )->get_table_name();

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT i.id, i.invoice_number, i.client_id, i.total, i.amount_due, i.due_date, i.status, i.created_at,
			        c.name AS client_name, c.email AS client_email
			FROM {$inv_table} i
			LEFT JOIN {$client_table} c ON c.id = i.client_id
			WHERE i.status IN ('sent', 'viewed', 'partial', 'overdue')
			ORDER BY i.due_date ASC
			LIMIT %d",
			10
		) ) ?: [];
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		set_transient( 'zinv_dash_pending_invoices', $rows, 5 * MINUTE_IN_SECONDS );
		return $rows;
	}
}
