<?php
/**
 * Class Noesia Cs Weather Service.
 *
 * @package Noesia_Commerce_Suite
 */

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

/**
 * Optional dashboard weather forecast service.
 *
 * Weather is disabled by default. When enabled, this service can use either
 * manually configured coordinates or a configured city/country search string.
 * Forecast and geocoding responses are cached locally so the dashboard does not
 * call external APIs on every admin page load.
 */
class Noesia_CS_Weather_Service {
	const FORECAST_TRANSIENT_PREFIX = 'noesia_cs_weather_forecast_v2_';
	const GEOCODE_TRANSIENT_PREFIX  = 'noesia_cs_weather_geocode_v1_';
	const LAST_GOOD_PREFIX          = 'noesia_cs_weather_last_good_v1_';

	/**
	 * Get a dashboard-safe weather forecast payload.
	 *
	 * @return array
	 */
	public function get_forecast() {
		try {
			$settings = Noesia_CS_Settings::get_all();

			if ( 'yes' !== ( $settings['dashboard_weather_enabled'] ?? 'no' ) ) {
				return array(
					'enabled'  => false,
					'status'   => 'disabled',
					'days'     => array(),
				);
			}

			$location_search  = sanitize_text_field( $settings['dashboard_weather_location_search'] ?? '' );
			$location_label   = sanitize_text_field( $settings['dashboard_weather_location_label'] ?? '' );
			$latitude         = $this->normalize_coordinate( $settings['dashboard_weather_latitude'] ?? '', -90, 90 );
			$longitude        = $this->normalize_coordinate( $settings['dashboard_weather_longitude'] ?? '', -180, 180 );

			if ( ( null === $latitude || null === $longitude ) && $location_search ) {
				$geocoded = $this->geocode_location( $location_search );
				if ( is_wp_error( $geocoded ) ) {
					return $this->error_payload( $settings, $geocoded->get_error_message() );
				}

				$latitude   = $geocoded['latitude'];
				$longitude  = $geocoded['longitude'];
				if ( '' === $location_label || __( 'Store Location', 'noesia-commerce-suite' ) === $location_label ) {
					$location_label = $geocoded['label'];
				}
			}

			if ( null === $latitude || null === $longitude ) {
				return array(
					'enabled'  => true,
					'status'   => 'missing_location',
					'message'  => __( 'Add a weather city/country search value or latitude and longitude in Settings to load the forecast.', 'noesia-commerce-suite' ),
					'days'     => array(),
				);
			}

			$cache_key  = $this->forecast_cache_key( $latitude, $longitude, $location_label );
			$cached     = get_transient( $cache_key );
			if ( is_array( $cached ) ) {
				return $cached;
			}

			$settings['dashboard_weather_location_label']  = $location_label;
			$payload                                       = $this->fetch_forecast( $latitude, $longitude, $settings );
			$minutes                                       = max( 30, absint( $settings['dashboard_weather_cache_minutes'] ?? 180 ) );

			if ( 'ok' === ( $payload['status'] ?? '' ) ) {
				set_transient( $cache_key, $payload, $minutes * MINUTE_IN_SECONDS );
				set_transient( $this->last_good_cache_key( $latitude, $longitude, $location_label ), $payload, DAY_IN_SECONDS );
				return $payload;
			}

			$last_good = get_transient( $this->last_good_cache_key( $latitude, $longitude, $location_label ) );
			if ( is_array( $last_good ) && ! empty( $last_good['days'] ) ) {
				$last_good['status']    = 'ok';
				$last_good['is_stale']  = true;
				$last_good['message']   = '';
				return $last_good;
			}

			set_transient( $cache_key, $payload, 30 * MINUTE_IN_SECONDS );
			return $payload;
		} catch ( Throwable $e ) {
			return array(
				'enabled'  => true,
				'status'   => 'error',
				'message'  => __( 'Weather forecast could not be loaded safely. Check the location settings or disable the module.', 'noesia-commerce-suite' ),
				'days'     => array(),
			);
		}
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $latitude Latitude.
	 * @param mixed $longitude Longitude.
	 * @param mixed $location_label Location label.
	 */
	private function forecast_cache_key( $latitude, $longitude, $location_label ) {
		return self::FORECAST_TRANSIENT_PREFIX . md5( (string) $latitude . '|' . (string) $longitude . '|' . sanitize_text_field( $location_label ) );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $latitude Latitude.
	 * @param mixed $longitude Longitude.
	 * @param mixed $location_label Location label.
	 */
	private function last_good_cache_key( $latitude, $longitude, $location_label ) {
		return self::LAST_GOOD_PREFIX . md5( (string) $latitude . '|' . (string) $longitude . '|' . sanitize_text_field( $location_label ) );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $query Query.
	 */
	private function geocode_location( $query ) {
		$query = trim( sanitize_text_field( $query ) );
		if ( '' === $query ) {
			return new WP_Error( 'noesia_cs_weather_missing_query', __( 'Weather location search is empty.', 'noesia-commerce-suite' ) );
		}

		$location_parts  = $this->parse_location_query( $query );
		$cache_key       = self::GEOCODE_TRANSIENT_PREFIX . md5( strtolower( $query ) );
		$cached          = get_transient( $cache_key );
		if ( is_array( $cached ) && isset( $cached['latitude'], $cached['longitude'], $cached['label'] ) ) {
			return $cached;
		}

		$args = array(
			'name'      => $location_parts['name'],
			'count'     => 10,
			'language'  => 'en',
			'format'    => 'json',
		);

		if ( ! empty( $location_parts['country_code'] ) ) {
			$args['country_code'] = $location_parts['country_code'];
		}

		$url = add_query_arg(
			$args,
			'https://geocoding-api.open-meteo.com/v1/search'
		);

		$response = $this->remote_get( $url );
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$body     = json_decode( wp_remote_retrieve_body( $response ), true );
		$results  = isset( $body['results'] ) && is_array( $body['results'] ) ? $body['results'] : array();
		$result   = $this->select_geocode_result( $results, $location_parts );
		if ( empty( $result ) || ! is_array( $result ) ) {
			return new WP_Error( 'noesia_cs_weather_location_not_found', __( 'Weather location was not found. Use a clearer city and country, or set latitude/longitude manually.', 'noesia-commerce-suite' ) );
		}
		$lat  = $this->normalize_coordinate( $result['latitude'] ?? '', -90, 90 );
		$lon  = $this->normalize_coordinate( $result['longitude'] ?? '', -180, 180 );
		if ( null === $lat || null === $lon ) {
			return new WP_Error( 'noesia_cs_weather_location_invalid', __( 'Weather location returned invalid coordinates.', 'noesia-commerce-suite' ) );
		}

		$label_parts = array_filter(
			array(
				sanitize_text_field( $result['name'] ?? '' ),
				sanitize_text_field( $result['admin1'] ?? '' ),
				sanitize_text_field( $result['country'] ?? '' ),
			)
		);
		$payload = array(
			'latitude'   => $lat,
			'longitude'  => $lon,
			'label'      => $label_parts ? implode( ', ', array_unique( $label_parts ) ) : $query,
		);

		set_transient( $cache_key, $payload, DAY_IN_SECONDS );
		return $payload;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $query Query.
	 */
	private function parse_location_query( $query ) {
		$parts  = array_map( 'trim', explode( ',', $query ) );
		$name   = sanitize_text_field( $parts[0] ?? $query );
		$tail   = sanitize_text_field( implode( ', ', array_slice( $parts, 1 ) ) );

		$country_code  = '';
		$tail_key      = strtolower( $tail );
		$aliases      = array(
			'usa'             => 'US',
			'united states'   => 'US',
			'uk'              => 'GB',
			'united kingdom'  => 'GB',
		);

		if ( preg_match( '/^[a-z]{2}$/i', $tail ) ) {
			$country_code = strtoupper( $tail );
		} elseif ( isset( $aliases[ $tail_key ] ) ) {
			$country_code = $aliases[ $tail_key ];
		}

		return array(
			'name'            => '' !== $name ? $name : $query,
			'country_filter'  => $tail,
			'country_code'    => $country_code,
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $results Results.
	 * @param mixed $location_parts Location parts.
	 */
	private function select_geocode_result( $results, $location_parts ) {
		if ( empty( $results ) || ! is_array( $results ) ) {
			return array();
		}

		$country_filter  = strtolower( sanitize_text_field( $location_parts['country_filter'] ?? '' ) );
		$country_code    = strtoupper( sanitize_text_field( $location_parts['country_code'] ?? '' ) );

		foreach ( $results as $result ) {
			if ( ! is_array( $result ) ) {
				continue;
			}

			$result_country_code  = strtoupper( sanitize_text_field( $result['country_code'] ?? '' ) );
			$result_country       = strtolower( sanitize_text_field( $result['country'] ?? '' ) );
			$result_admin         = strtolower( sanitize_text_field( $result['admin1'] ?? '' ) );

			if ( $country_code && $country_code === $result_country_code ) {
				return $result;
			}

			if ( $country_filter && ( $country_filter === $result_country || $country_filter === $result_admin ) ) {
				return $result;
			}
		}

		return $results[0];
	}

	/**
	 * Fetch forecast data from Open-Meteo.
	 *
	 * @param float $latitude Latitude.
	 * @param float $longitude Longitude.
	 * @param array $settings Settings.
	 * @return array
	 */
	private function fetch_forecast( $latitude, $longitude, $settings ) {
		$url = add_query_arg(
			array(
				'latitude'       => $latitude,
				'longitude'      => $longitude,
				'daily'          => 'weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max',
				'timezone'       => 'auto',
				'forecast_days'  => 7,
			),
			'https://api.open-meteo.com/v1/forecast'
		);

		$response = $this->remote_get( $url );
		if ( is_wp_error( $response ) ) {
			return $this->error_payload( $settings, $response->get_error_message() );
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( ! is_array( $body ) || empty( $body['daily'] ) || ! is_array( $body['daily'] ) ) {
			return $this->error_payload( $settings, __( 'Weather response did not include a daily forecast.', 'noesia-commerce-suite' ) );
		}

		$daily  = $body['daily'];
		$days   = array();
		$times  = isset( $daily['time'] ) && is_array( $daily['time'] ) ? $daily['time'] : array();

		foreach ( $times as $index => $date ) {
			$weather_code = absint( $daily['weather_code'][ $index ] ?? 0 );
			$days[] = array(
				'date'       => sanitize_text_field( $date ),
				'label'      => $this->day_label( $date, $index ),
				'condition'  => $this->weather_label( $weather_code ),
				'icon'       => $this->weather_icon( $weather_code ),
				'max'        => isset( $daily['temperature_2m_max'][ $index ] ) ? round( (float) $daily['temperature_2m_max'][ $index ] ) : null,
				'min'        => isset( $daily['temperature_2m_min'][ $index ] ) ? round( (float) $daily['temperature_2m_min'][ $index ] ) : null,
				'rain'       => isset( $daily['precipitation_probability_max'][ $index ] ) ? absint( $daily['precipitation_probability_max'][ $index ] ) : null,
				'is_today'   => 0 === (int) $index,
			);
		}

		if ( empty( $days ) ) {
			return $this->error_payload( $settings, __( 'Weather response did not include forecast days.', 'noesia-commerce-suite' ) );
		}

		return array(
			'enabled'       => true,
			'status'        => 'ok',
			'location'      => sanitize_text_field( $settings['dashboard_weather_location_label'] ?? '' ),
			'latitude'      => $latitude,
			'longitude'     => $longitude,
			'days'          => $days,
			'fetched_at'    => time(),
			'external_api'  => 'Open-Meteo',
			'disclosure'    => __( 'Weather data loads from the external Open-Meteo API only when this dashboard module is enabled.', 'noesia-commerce-suite' ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $url Url.
	 */
	private function remote_get( $url ) {
		$response = wp_remote_get(
			$url,
			array(
				'timeout'      => 8,
				'redirection'  => 2,
				'user-agent'   => 'Noesia Commerce Suite/' . NOESIA_CS_VERSION . '; ' . home_url( '/' ),
			)
		);

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

		$code = absint( wp_remote_retrieve_response_code( $response ) );
		if ( 200 !== $code ) {
			return new WP_Error(
				'noesia_cs_weather_http_error',
				sprintf(
					/* translators: %d: HTTP response code. */
					__( 'Open-Meteo returned HTTP %d.', 'noesia-commerce-suite' ),
					$code
				)
			);
		}

		return $response;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $settings Settings.
	 * @param mixed $message Message.
	 */
	private function error_payload( $settings, $message ) {
		return array(
			'enabled'       => true,
			'status'        => 'error',
			'location'      => sanitize_text_field( $settings['dashboard_weather_location_label'] ?? '' ),
			'message'       => sanitize_text_field( $message ),
			'days'          => array(),
			'external_api'  => 'Open-Meteo',
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $value Value.
	 * @param mixed $min Min.
	 * @param mixed $max Max.
	 */
	private function normalize_coordinate( $value, $min, $max ) {
		if ( '' === $value || null === $value ) {
			return null;
		}
		$value = (float) $value;
		if ( $value < $min || $value > $max ) {
			return null;
		}
		return $value;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $date Date.
	 * @param mixed $index Index.
	 */
	private function day_label( $date, $index ) {
		if ( 0 === (int) $index ) {
			return __( 'Today', 'noesia-commerce-suite' );
		}
		$timestamp = strtotime( $date );
		return $timestamp ? wp_date( 'l', $timestamp ) : sanitize_text_field( $date );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	private function weather_label( $code ) {
		$labels = array(
			0   => __( 'Clear', 'noesia-commerce-suite' ),
			1   => __( 'Mostly Clear', 'noesia-commerce-suite' ),
			2   => __( 'Partly Cloudy', 'noesia-commerce-suite' ),
			3   => __( 'Cloudy', 'noesia-commerce-suite' ),
			45  => __( 'Fog', 'noesia-commerce-suite' ),
			48  => __( 'Rime Fog', 'noesia-commerce-suite' ),
			51  => __( 'Light Drizzle', 'noesia-commerce-suite' ),
			53  => __( 'Drizzle', 'noesia-commerce-suite' ),
			55  => __( 'Heavy Drizzle', 'noesia-commerce-suite' ),
			61  => __( 'Light Rain', 'noesia-commerce-suite' ),
			63  => __( 'Rain', 'noesia-commerce-suite' ),
			65  => __( 'Heavy Rain', 'noesia-commerce-suite' ),
			71  => __( 'Light Snow', 'noesia-commerce-suite' ),
			73  => __( 'Snow', 'noesia-commerce-suite' ),
			75  => __( 'Heavy Snow', 'noesia-commerce-suite' ),
			80  => __( 'Rain Showers', 'noesia-commerce-suite' ),
			81  => __( 'Rain Showers', 'noesia-commerce-suite' ),
			82  => __( 'Heavy Showers', 'noesia-commerce-suite' ),
			95  => __( 'Thunderstorms', 'noesia-commerce-suite' ),
			96  => __( 'Thunderstorms', 'noesia-commerce-suite' ),
			99  => __( 'Thunderstorms', 'noesia-commerce-suite' ),
		);
		return $labels[ $code ] ?? __( 'Forecast', 'noesia-commerce-suite' );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $code Code.
	 */
	private function weather_icon( $code ) {
		if ( 0 === $code || 1 === $code ) {
			return '☀';
		}
		if ( in_array( $code, array( 2, 3 ), true ) ) {
			return '☁';
		}
		if ( in_array( $code, array( 45, 48 ), true ) ) {
			return '≋';
		}
		if ( in_array( $code, array( 95, 96, 99 ), true ) ) {
			return 'ϟ';
		}
		if ( $code >= 71 && $code <= 77 ) {
			return '❄';
		}
		if ( $code >= 51 && $code <= 82 ) {
			return '☂';
		}
		return '•';
	}
}
