<?php

namespace SugarCalendarBookings\SetupWizard;

use DateTime;
use DateTimeZone;
use SugarCalendarBookings\Employees\EmployeeModel;
use SugarCalendarBookings\Locations\Admin\LocationFormService as LiteLocationFormService;
use SugarCalendarBookings\Locations\CreateLocationDTO;
use SugarCalendarBookings\Locations\LocationModel;
use SugarCalendarBookings\Locations\LocationService;
use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Services\Admin\ServiceFormService as LiteServiceFormService;
use SugarCalendarBookings\Services\CreateServiceDTO;
use SugarCalendarBookings\Services\ServiceService;
use SugarCalendarBookings\Support\Data\Countries;
use SugarCalendarBookings\Support\Data\Currencies;
use SugarCalendarBookings\Support\Data\DateTime as DateTimeData;
use SugarCalendarBookings\Support\DataProcessing\InputProcessor;
use SugarCalendarBookings\Support\Helpers\Date;
use SugarCalendarBookings\Support\Helpers\Helpers;
use SugarCalendarBookings\Support\Helpers\ImageUploader;
use SugarCalendarBookings\Support\Helpers\UI;
use WP_Error;

/**
 * Class SetupWizardService.
 *
 * Business logic for Setup Wizard settings, options, and entity creation.
 *
 * @since 1.2.0
 */
class SetupWizardService {

	/**
	 * Get plugin settings for hydration.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	public function get_settings() {

		$options = Plugin::get( Options::class );

		return [
			'core'     => $this->get_core_settings(),
			'bookings' => $this->get_bookings_settings(),
			'stripe'   => Plugin::get( StripeSetupService::class )->get_settings(),
			'license'  => [
				'type' => $options->get( 'license.type', 'lite' ),
				'key'  => $options->get( 'license.key', '' ),
			],
			'plugins'  => [
				'smtp'   => Plugin::get( PluginInstallerService::class )->detect_smtp_plugins(),
				'addons' => [],
			],
		];
	}

	/**
	 * Get plugin options for hydration.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	public function get_options() {

		$options  = Plugin::get( Options::class );
		$timezone = Date::get_default_timezone_string();

		// Only return IANA city-based timezones (e.g., America/New_York).
		// If the site uses a manual UTC offset (e.g., +05:00), return empty
		// so the frontend can autodetect via the browser's Intl API.
		if ( strpos( $timezone, '/' ) === false ) {
			$timezone = '';
		}

		return [
			'timezone'     => $timezone,
			'display'      => [
				'date_format'   => $options->get( 'display.date_format' ),
				'time_format'   => $options->get( 'display.time_format' ),
				'start_of_week' => (string) $options->get( 'display.start_of_week' ),
			],
			'employee'     => $this->get_employee_data(),
			'payments'     => [
				'currency' => $options->get( 'payments.currency', 'USD' ),
			],
			'stripe'       => [
				'test_mode' => $options->get( 'stripe.test_mode', false ),
			],
			'availability' => [
				'working_schedule' => [
					'regular' => $options->get( 'availability.working_schedule.regular', [] ),
				],
			],
		];
	}

	/**
	 * Update plugin timezone option.
	 *
	 * @since 1.2.0
	 *
	 * @param string $timezone Timezone value (named or UTC offset).
	 *
	 * @return bool True if timezone was updated, false if invalid.
	 */
	public function update_timezone( $timezone ) {

		if ( ! Date::is_valid_timezone( $timezone ) ) {
			return false;
		}

		Plugin::get( Options::class )->update( [ 'display' => [ 'timezone' => $timezone ] ] );

		Date::reset_timezone_cache();

		return true;
	}

	/**
	 * Update employee data.
	 *
	 * Updates the default employee in the database, or creates one if it doesn't exist.
	 *
	 * @since 1.2.0
	 *
	 * @param array $data Employee data (name, email, phone).
	 *
	 * @return bool|WP_Error True on success, WP_Error on validation failure, false on save failure.
	 */
	public function update_employee( $data ) {

		if ( empty( $data ) ) {
			return false;
		}

		// Define validation and sanitization rules.
		$rules = [
			'name'  => 'required|string|trim',
			'email' => 'required|string|email|trim',
			'phone' => 'nullable|string|trim',
		];

		// Process input data.
		$processed = InputProcessor::process( $data, $rules );

		if ( is_wp_error( $processed ) ) {
			return $processed;
		}

		// Get or create the default employee.
		$employee = EmployeeModel::query()->order_by( 'id', 'asc' )->first();

		if ( ! $employee ) {
			$employee = new EmployeeModel();

			// Set user_id if creating new employee and user is logged in.
			$user = wp_get_current_user();

			if ( $user->ID > 0 ) {
				$employee->user_id = $user->ID;
			}
		}

		// Update employee data with processed values.
		$employee->name  = $processed['name'];
		$employee->email = $processed['email'];
		$employee->phone = $processed['phone'] ?? '';

		return $employee->save();
	}

	/**
	 * Create a location from data.
	 *
	 * @since 1.2.0
	 *
	 * @param array|null $data Location data.
	 *
	 * @return int|null|WP_Error Location ID on success, null if no data, WP_Error on failure.
	 */
	public function create_location( $data ) {

		if ( empty( $data ) ) {
			return null;
		}

		// Use Lite rules for Setup Wizard since we're only creating basic locations.
		$rules     = ( new LiteLocationFormService() )->rules();
		$processed = InputProcessor::process( $data, $rules );

		if ( is_wp_error( $processed ) ) {
			return new WP_Error(
				'invalid_data',
				__( 'Invalid location data.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		$location = Plugin::get( LocationService::class )->create( new CreateLocationDTO( $processed ) );

		if ( ! $location ) {
			return new WP_Error(
				'creation_failed',
				__( 'Failed to create location.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		return $location->id;
	}

	/**
	 * Create a service.
	 *
	 * @since 1.2.0
	 *
	 * @param array    $data        Service data.
	 * @param int|null $location_id Location ID to assign.
	 *
	 * @return true|WP_Error True on success, WP_Error on failure.
	 */
	public function create_service( $data, $location_id = null ) {

		$data = $this->prepare_service_data( $data, $location_id );

		// Use Lite rules for Setup Wizard since we're only creating basic services.
		$rules     = ( new LiteServiceFormService() )->rules();
		$processed = InputProcessor::process( $data, $rules );

		if ( is_wp_error( $processed ) ) {
			return new WP_Error(
				'invalid_data',
				__( 'Invalid service data.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		$service = ( new ServiceService() )->create( new CreateServiceDTO( $processed ) );

		if ( ! $service ) {
			return new WP_Error(
				'creation_failed',
				__( 'Failed to create service.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		return true;
	}

	/**
	 * Get core settings.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	private function get_core_settings() {

		$timezone = Date::get_default_timezone_string();

		return [
			'date_formats'          => $this->get_formatted_datetime( $timezone, DateTimeData::get_date_formats() ),
			'time_formats'          => $this->get_formatted_datetime( $timezone, DateTimeData::get_time_formats() ),
			'week_days'             => $this->get_weekdays(),
			'timezones'             => $this->get_timezones(),
			'admin_email'           => get_option( 'admin_email' ),
			'complete_url'          => admin_url( 'admin.php?page=sugar-calendar-bookings-calendar&add-appointment=1' ),
			'user_agent'            => Helpers::get_default_user_agent(),
			'is_pro'                => sugar_calendar_bookings()->is_pro(),
			'plugin_version'        => SCBOOKINGS_PLUGIN_VERSION,
			'has_existing_location' => Plugin::get( LocationService::class )->get_first_enabled() !== null,
			'documentation_url'     => Helpers::get_utm_url(
				'https://sugarcalendar.com/docs/bookings/',
				[
					'medium'  => 'setup-wizard',
					'content' => 'documentation',
				]
			),
			'upgrade_url'           => Helpers::get_utm_url(
				'https://sugarcalendar.com/pricing/bookings/',
				[
					'medium'  => 'setup-wizard',
					'content' => 'upgrade',
				]
			),
			'terms_url'             => Helpers::get_utm_url(
				'https://sugarcalendar.com/terms/',
				[
					'medium' => 'setup-wizard',
				]
			),
		];
	}

	/**
	 * Get bookings settings.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	private function get_bookings_settings() {

		return [
			'currencies' => Currencies::get_all(),
			'countries'  => $this->get_countries(),
		];
	}

	/**
	 * Get employee data for Setup Wizard.
	 *
	 * Fetches the default employee from the database if it exists, or generates
	 * default data from the current user/admin email.
	 *
	 * @since 1.2.0
	 *
	 * @return array Employee data with name, email, and phone.
	 */
	private function get_employee_data() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$options = Plugin::get( Options::class );

		// Try to get the first employee from the database.
		$employee = EmployeeModel::query()->order_by( 'id', 'asc' )->first();

		if ( $employee ) {
			return [
				'name'  => $employee->name,
				'email' => $employee->email,
				'phone' => $employee->phone ?? '',
			];
		}

		// Fallback to current user or default values.
		$user = wp_get_current_user();
		$name = '';

		if ( $user->ID > 0 ) {
			$name = ! empty( $user->first_name ) ? $user->first_name : $user->nickname;
		}

		// If still no name, check options (user might have set it in wizard).
		if ( empty( $name ) ) {
			$name = $options->get( 'employee.name', '' );
		}

		return [
			'name'  => $name,
			'email' => $user->ID > 0 ? $user->user_email : get_option( 'admin_email' ),
			'phone' => $options->get( 'employee.phone', '' ),
		];
	}

	/**
	 * Get countries list formatted for frontend.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	private function get_countries() {

		$countries            = Countries::get_all();
		$default_country_code = Countries::get_by_locale( get_locale() );

		return [
			'list'                 => array_map(
				fn( $code, $name ) => [
					'code' => $code,
					'name' => $name,
				],
				array_keys( $countries ),
				$countries
			),
			'default_country_code' => $default_country_code,
		];
	}

	/**
	 * Prepare service data with defaults, image upload, and location assignment.
	 *
	 * @since 1.2.0
	 *
	 * @param array    $data        Service data.
	 * @param int|null $location_id Location ID to assign.
	 *
	 * @return array Prepared service data.
	 */
	private function prepare_service_data( $data, $location_id = null ) {

		// Set defaults.
		$data['color'] = $data['color'] ?? '#3498db';

		// Handle base64 image upload.
		$data = $this->process_service_image( $data );

		// Assign location to service if provided.
		if ( $location_id ) {
			$data['locations'] = [
				[
					'id'    => $location_id,
					'order' => 1,
				],
			];
		}

		return $data;
	}

	/**
	 * Process service image from base64 data.
	 *
	 * @since 1.2.0
	 *
	 * @param array $data Service data.
	 *
	 * @return array Service data with feature_image_id set if image was uploaded.
	 */
	private function process_service_image( $data ) {

		if ( empty( $data['image_base64'] ) ) {
			return $data;
		}

		$attachment_id = ImageUploader::upload_base64( $data['image_base64'], $data['name'] ?? 'service' );

		if ( $attachment_id ) {
			$data['feature_image_id'] = $attachment_id;
		}

		unset( $data['image_base64'] );

		return $data;
	}

	/**
	 * Get formatted date/time settings.
	 *
	 * @since 1.2.0
	 *
	 * @param string $timezone Current timezone.
	 * @param array  $formats  Formats array.
	 *
	 * @return array
	 */
	private function get_formatted_datetime( $timezone, $formats ) {

		return array_map(
			function ( $format ) use ( $timezone ) {

				$datetime = new DateTime( 'now', new DateTimeZone( $timezone ) );

				return [
					'value' => $format,
					'label' => $datetime->format( $format ),
				];
			},
			$formats
		);
	}

	/**
	 * Get days of the week.
	 *
	 * Uses 1-7 convention (Monday = 1, Sunday = 7) to match WorkingScheduleDefaults.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	private function get_weekdays() {

		global $wp_locale;

		// Use 1-7 convention (Monday = 1, Sunday = 7) to match the rest of the plugin.
		$day_indices = [ 1, 2, 3, 4, 5, 6, 7 ];

		return array_map(
			function ( $day ) use ( $wp_locale ) {

				// Convert to 0-6 for wp_locale (Sunday = 0).
				$wp_locale_day = $day === 7 ? 0 : $day;

				return [
					'value' => (string) $day,
					'label' => $wp_locale->get_weekday( $wp_locale_day ),
				];
			},
			$day_indices
		);
	}

	/**
	 * Get available timezones as IANA city-based list.
	 *
	 * Delegates to UI::get_timezone_cities() which returns timezone identifiers
	 * sorted alphabetically by city name with static caching.
	 *
	 * @since 1.3.1
	 *
	 * @return array
	 */
	private function get_timezones() {

		return UI::get_timezone_cities();
	}
}
