<?php
/**
 * Report Service
 *
 * Handles cron scheduling, HTML generation, and email sending for digest reports.
 *
 * @package EmailReports
 * @since 1.0.0
 */

namespace EmailReports\Includes;

use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;

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

class ReportService {

	private const MODULE_ID = 'email-reports';
	private const CRON_HOOK_DAILY   = 'wpps_email_reports_daily';
	private const CRON_HOOK_WEEKLY  = 'wpps_email_reports_weekly';
	private const CRON_HOOK_MONTHLY = 'wpps_email_reports_monthly';

	public static function getSettings(): array {
		$defaults = [
			'enabled'           => false,
			'schedule'          => 'weekly',
			'day_of_week'       => 1,
			'day_of_month'      => 1,
			'time'              => '09:00',
			'subject_format'    => '',
			'recipients_type'   => 'admin',
			'custom_recipients' => '',
			'sections'          => [],
			'logo_id'           => 0,
			'company_name'      => '',
			'primary_color'     => '#2271b1',
			'footer_text'       => '',
		];
		$raw = OptionStoreModuleSettingsRepository::forModule( self::MODULE_ID )->all( [] );
		$parsed = wp_parse_args( is_array( $raw ) ? $raw : [], $defaults );
		$parsed['enabled'] = ! empty( $parsed['enabled'] ) && $parsed['enabled'] !== '0';
		return $parsed;
	}

	public static function getRecipients(): array {
		$s = self::getSettings();
		if ( ( $s['recipients_type'] ?? '' ) === 'custom' && ! empty( $s['custom_recipients'] ) ) {
			$emails = array_values(
				array_unique(
					array_filter( array_map( 'trim', explode( "\n", (string) $s['custom_recipients'] ) ), 'is_email' )
				)
			);
			// Cap to reduce accidental mass-mail / quota abuse from settings.
			return array_slice( $emails, 0, 20 );
		}
		$admin = get_option( 'admin_email' );
		return $admin ? [ $admin ] : [];
	}

	public static function clearScheduledHooks(): void {
		wp_clear_scheduled_hook( self::CRON_HOOK_DAILY );
		wp_clear_scheduled_hook( self::CRON_HOOK_WEEKLY );
		wp_clear_scheduled_hook( self::CRON_HOOK_MONTHLY );
	}

	public static function scheduleReports(): void {
		self::clearScheduledHooks();

		$s = self::getSettings();
		if ( empty( $s['enabled'] ) ) {
			return;
		}

		$schedule = $s['schedule'] ?? 'weekly';
		$next     = self::calculateNextRun( $s );

		$hook = match ( $schedule ) {
			'daily'   => self::CRON_HOOK_DAILY,
			'monthly' => self::CRON_HOOK_MONTHLY,
			default   => self::CRON_HOOK_WEEKLY,
		};

		wp_schedule_event( $next, $schedule, $hook, [], true );
	}

	private static function calculateNextRun( array $s ): int {
		$time  = $s['time'] ?? '09:00';
		$parts = explode( ':', $time );
		$hour  = (int) ( $parts[0] ?? 9 );
		$min   = (int) ( $parts[1] ?? 0 );
		$tz    = wp_timezone();
		$now   = new \DateTimeImmutable( 'now', $tz );

		$schedule = $s['schedule'] ?? 'weekly';

		if ( $schedule === 'daily' ) {
			$target = $now->setTime( $hour, $min, 0 );
			if ( $now >= $target ) {
				$target = $target->modify( '+1 day' );
			}
			return $target->getTimestamp();
		}

		if ( $schedule === 'weekly' ) {
			$dow         = (int) ( $s['day_of_week'] ?? 1 );
			$current_dow = (int) $now->format( 'w' );
			$days_until  = ( $dow - $current_dow + 7 ) % 7;
			$target      = $now->modify( "+{$days_until} days" )->setTime( $hour, $min, 0 );
			if ( $days_until === 0 && $now >= $target ) {
				$target = $target->modify( '+7 days' );
			}
			return $target->getTimestamp();
		}

		if ( $schedule === 'monthly' ) {
			$dom = (int) ( $s['day_of_month'] ?? 1 );
			$y   = (int) $now->format( 'Y' );
			$m   = (int) $now->format( 'n' );
			$max = cal_days_in_month( CAL_GREGORIAN, $m, $y );
			$d   = min( $dom, $max );
			$target = $now->setDate( $y, $m, $d )->setTime( $hour, $min, 0 );
			if ( $now >= $target ) {
				$m++;
				if ( $m > 12 ) {
					$m = 1;
					$y++;
				}
				$max = cal_days_in_month( CAL_GREGORIAN, $m, $y );
				$d   = min( $dom, $max );
				$target = $now->setDate( $y, $m, $d )->setTime( $hour, $min, 0 );
			}
			return $target->getTimestamp();
		}

		return time() + DAY_IN_SECONDS;
	}

	public static function addCronSchedules( array $schedules ): array {
		$schedules['weekly'] = [
			'interval' => WEEK_IN_SECONDS,
			'display'  => __( 'Once Weekly', 'wp-powersuite' ),
		];
		$schedules['monthly'] = [
			'interval' => MONTH_IN_SECONDS,
			'display'  => __( 'Once Monthly', 'wp-powersuite' ),
		];
		return $schedules;
	}

	public static function getDateRange( string $schedule ): array {
		$tz  = wp_timezone();
		$now = new \DateTimeImmutable( 'now', $tz );

		if ( $schedule === 'daily' ) {
			$from_dt = $now->modify( '-1 day' )->setTime( 0, 0, 0 );
			$to_dt   = $now->setTime( 23, 59, 59 );
		} elseif ( $schedule === 'monthly' ) {
			$from_dt = $now->modify( 'first day of last month' )->setTime( 0, 0, 0 );
			$to_dt   = $now->modify( 'last day of last month' )->setTime( 23, 59, 59 );
		} else {
			$from_dt = $now->modify( '-7 days' )->setTime( 0, 0, 0 );
			$to_dt   = $now->setTime( 23, 59, 59 );
		}

		return [
			'from'          => $from_dt->format( 'Y-m-d' ),
			'to'            => $to_dt->format( 'Y-m-d' ),
			'from_datetime' => $from_dt->format( 'Y-m-d H:i:s' ),
			'to_datetime'   => $to_dt->format( 'Y-m-d H:i:s' ),
		];
	}

	/**
	 * Send the email report.
	 *
	 * @param bool $force When true, bypasses the "Enable Reports" check (for manual Send Now).
	 * @return bool True if sent successfully.
	 */
	public static function sendReport( bool $force = false ): bool {
		$s = self::getSettings();
		if ( ! $force && empty( $s['enabled'] ) ) {
			return false;
		}

		$recipients = self::getRecipients();
		if ( empty( $recipients ) ) {
			return false;
		}

		$registry = ReportRegistry::getInstance();
		$registry->init();

		$section_ids = ! empty( $s['sections'] ) ? $s['sections'] : array_keys( $registry->getAvailableSections() );
		if ( empty( $section_ids ) ) {
			return false;
		}

		$schedule   = $s['schedule'] ?? 'weekly';
		$date_range = self::getDateRange( $schedule );
		$sections_data = $registry->getSectionsData( $section_ids, $date_range );

		if ( empty( $sections_data ) ) {
			$sections_data = self::buildEmptySectionsData( $date_range );
		}

		$html    = self::buildHtml( $sections_data, $date_range, $s );
		$subject = self::buildSubject( $date_range, $s );
		$plain   = self::buildPlainText( $sections_data, $date_range );

		return self::sendMail( $recipients, $subject, $html, $plain );
	}

	/**
	 * Send a test report using saved section settings.
	 *
	 * @param bool $is_test Prefix subject with [Test].
	 * @return bool
	 */
	public static function sendTestReport( bool $is_test = true ): bool {
		$s = self::getSettings();
		$recipients = self::getRecipients();
		if ( empty( $recipients ) ) {
			return false;
		}

		$registry = ReportRegistry::getInstance();
		$registry->init();

		$section_ids = ! empty( $s['sections'] ) ? $s['sections'] : array_keys( $registry->getAvailableSections() );
		if ( empty( $section_ids ) ) {
			return false;
		}

		$date_range    = self::getDateRange( $s['schedule'] ?? 'weekly' );
		$sections_data = $registry->getSectionsData( $section_ids, $date_range );
		if ( empty( $sections_data ) ) {
			$sections_data = self::buildEmptySectionsData( $date_range );
		}

		$subject = self::buildSubject( $date_range, $s );
		if ( $is_test ) {
			$subject = '[Test] ' . $subject;
		}

		$html  = self::buildHtml( $sections_data, $date_range, $s );
		$plain = self::buildPlainText( $sections_data, $date_range );

		return self::sendMail( $recipients, $subject, $html, $plain );
	}

	/**
	 * @param array<string, mixed> $sections_data
	 * @param array<string, string> $date_range
	 * @param array<string, mixed> $s
	 */
	public static function buildHtml( array $sections_data, array $date_range, array $s ): string {
		$color = $s['primary_color'] ?? '#2271b1';
		if ( ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/', (string) $color ) ) {
			$color = '#2271b1';
		}

		$company  = ! empty( $s['company_name'] ) ? esc_html( $s['company_name'] ) : esc_html( get_bloginfo( 'name' ) );
		$logo_url = '';
		if ( ! empty( $s['logo_id'] ) ) {
			$logo_url = wp_get_attachment_image_url( (int) $s['logo_id'], 'medium' );
		}
		$footer = ! empty( $s['footer_text'] ) ? wp_kses_post( $s['footer_text'] ) : '';

		ob_start();
		include __DIR__ . '/../views/email-template.php';
		return ob_get_clean();
	}

	public static function buildSubject( array $date_range, array $s ): string {
		$site    = get_bloginfo( 'name' );
		$company = ! empty( $s['company_name'] ) ? $s['company_name'] : $site;
		$from    = $date_range['from'];
		$to      = $date_range['to'];
		$format  = trim( $s['subject_format'] ?? '' );
		if ( $format === '' ) {
			$format = __( '%1$s – Site Report (%2$s to %3$s)', 'wp-powersuite' );
		}
		return sprintf( $format, $company, $from, $to );
	}

	/**
	 * @param array<string, string> $date_range
	 * @return array<string, array<string, mixed>>
	 */
	private static function buildEmptySectionsData( array $date_range ): array {
		return [
			'summary' => [
				'title'       => __( 'Summary', 'wp-powersuite' ),
				'description' => '',
				'icon'        => 'chart-bar',
				'data'        => [
					'message' => sprintf(
						__( 'No activity recorded between %1$s and %2$s.', 'wp-powersuite' ),
						$date_range['from'],
						$date_range['to']
					),
				],
			],
		];
	}

	/**
	 * @param array<string, mixed> $sections_data
	 * @param array<string, string> $date_range
	 */
	private static function buildPlainText( array $sections_data, array $date_range ): string {
		$lines = [
			sprintf( __( 'Site Report: %s to %s', 'wp-powersuite' ), $date_range['from'], $date_range['to'] ),
			'',
		];
		foreach ( $sections_data as $id => $section ) {
			$lines[] = '--- ' . $section['title'] . ' ---';
			$lines[] = self::sectionToPlainText( $id, $section['data'] );
			$lines[] = '';
		}
		return implode( "\n", $lines );
	}

	private static function sectionToPlainText( string $section_id, array $data ): string {
		$lines = [];
		switch ( $section_id ) {
			case 'summary':
				$lines[] = $data['message'] ?? '';
				break;
			case 'track-404s':
				$lines[] = sprintf( __( 'Total 404s: %d', 'wp-powersuite' ), $data['total_404s'] ?? 0 );
				$lines[] = sprintf( __( 'Unique URLs: %d', 'wp-powersuite' ), $data['unique_urls'] ?? 0 );
				foreach ( $data['top_404_urls'] ?? [] as $row ) {
					$lines[] = '  - ' . ( $row['url'] ?? '' ) . ' (' . ( $row['hits'] ?? 0 ) . ')';
				}
				break;
			case 'limit-login-attempts':
				$lines[] = sprintf( __( 'Failed attempts: %d', 'wp-powersuite' ), $data['failed_attempts'] ?? 0 );
				$lines[] = sprintf( __( 'Lockouts: %d', 'wp-powersuite' ), $data['lockouts'] ?? 0 );
				foreach ( $data['top_blocked_ips'] ?? [] as $row ) {
					$lines[] = '  - ' . ( $row['ip'] ?? '' ) . ' (' . ( $row['count'] ?? 0 ) . ')';
				}
				break;
			case 'activity-log':
				$lines[] = sprintf( __( 'Total events: %d', 'wp-powersuite' ), $data['total_events'] ?? 0 );
				$lines[] = sprintf( __( 'Logins: %d', 'wp-powersuite' ), $data['user_logins'] ?? 0 );
				$lines[] = sprintf( __( 'Failed logins: %d', 'wp-powersuite' ), $data['failed_logins'] ?? 0 );
				$lines[] = sprintf( __( 'Content changes: %d', 'wp-powersuite' ), $data['content_changes'] ?? 0 );
				break;
			case 'email-logs':
				$lines[] = sprintf( __( 'Sent: %d', 'wp-powersuite' ), $data['total_sent'] ?? 0 );
				$lines[] = sprintf( __( 'Delivered: %d', 'wp-powersuite' ), $data['total_delivered'] ?? 0 );
				$lines[] = sprintf( __( 'Failed: %d', 'wp-powersuite' ), $data['total_failed'] ?? 0 );
				$lines[] = sprintf( __( 'Bounced: %d', 'wp-powersuite' ), $data['total_bounced'] ?? 0 );
				break;
			default:
				$lines[] = wp_json_encode( $data );
		}
		return implode( "\n", $lines );
	}

	/**
	 * @param array<int, string> $recipients
	 */
	private static function sendMail( array $recipients, string $subject, string $html, string $plain ): bool {
		$report_header = 'X-WP-PowerSuite-Report: 1';
		$html_headers  = [ 'Content-Type: text/html; charset=UTF-8', $report_header ];
		$sent          = wp_mail( $recipients, $subject, $html, $html_headers );

		if ( ! $sent ) {
			$sent = wp_mail(
				$recipients,
				$subject,
				$plain,
				[ 'Content-Type: text/plain; charset=UTF-8', $report_header ]
			);
		}

		return $sent;
	}
}
