<?php

declare(strict_types=1);

namespace FormConnector\Integration\Forms;

use FormConnector\Integration\CareHQ\APIClient;
use FormConnector\Integration\Forms\Adapters\AbstractAdapter;
use FormConnector\Integration\Forms\Adapters\GravityFormsAdapter;
use FormConnector\Integration\Forms\Adapters\ContactForm7Adapter;
use FormConnector\Integration\Forms\Adapters\WPFormsAdapter;
use FormConnector\Integration\Forms\Adapters\ForminatorAdapter;
use FormConnector\Integration\Forms\Adapters\FluentFormsAdapter;
use FormConnector\Features\Homes\Homes;
use FormConnector\Features\License\License;
use FormConnector\Features\Logs\Logs;
use FormConnector\Features\UTM\UTMDataReader;
use FormConnector\Core\Validation\PhoneValidator;
use FormConnector\Features\Retry\RetryQueue;
use FormConnector\Features\Notifications\EmailNotifier;
use FormConnector\Features\Analytics\SubmissionTracker;

/**
 * Form Submission Handler
 * 
 * Coordinates form adapters and processes submissions to CareHQ.
 * Uses separate adapter classes for each form plugin.
 */
class SubmissionHandler {

	/**
	 * Singleton instance
	 */
	private static ?SubmissionHandler $instance = null;

	/**
	 * License manager
	 */
	private License $license;

	/**
	 * Registered adapters
	 * @var AbstractAdapter[]
	 */
	private array $adapters = [];

	/**
	 * Mappings manager
	 */
	private Mappings $mappings;

	/**
	 * API client
	 */
	private APIClient $api;

	/**
	 * Logs manager
	 */
	private Logs $logs;

	/**
	 * Phone validator
	 */
	private PhoneValidator $phone_validator;

	/**
	 * UTM data reader
	 */
	private UTMDataReader $utm_reader;

	/**
	 * Cached sales channel ID (avoids repeated DB lookups)
	 */
	private ?string $sales_channel_cache = null;

	/**
	 * CareHQ standard fields
	 */
	private const CAREHQ_FIELDS = [
		'first_name' => 'First Name',
		'last_name'  => 'Last Name',
		'email'      => 'Email Address',
		'phone'      => 'Phone Number',
		'care_requirements' => 'Message / Enquiry Details',
	];

	/**
	 * Get singleton instance
	 */
	public static function get_instance(): self {
		if (self::$instance === null) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor
	 */
	private function __construct() {
		$this->mappings = new Mappings();
		$this->api = APIClient::get_instance();
		$this->logs = Logs::get_instance();
		$this->phone_validator = new PhoneValidator();
		$this->utm_reader = new UTMDataReader();
		$this->license = new License();

		// Register all adapters
		$this->register_adapters();
	}

	/**
	 * Register all form adapters
	 */
	private function register_adapters(): void {
		$this->adapters = [
			new GravityFormsAdapter(),
			new ContactForm7Adapter(),
			new WPFormsAdapter(),
			new ForminatorAdapter(),
			new FluentFormsAdapter(),
		];

		// Set the submission callback on each adapter
		foreach ($this->adapters as $adapter) {
			$adapter->set_submission_callback([$this, 'process_submission']);
		}
	}

	/**
	 * Initialize all form hooks
	 */
	public function init(): void {
		foreach ($this->adapters as $adapter) {
			if ($adapter->is_active()) {
				$adapter->register_hooks();
			}
		}
	}

	/**
	 * Get all registered adapters
	 *
	 * @return AbstractAdapter[]
	 */
	public function get_adapters(): array {
		return $this->adapters;
	}

	/**
	 * Get active adapters only
	 *
	 * @return AbstractAdapter[]
	 */
	public function get_active_adapters(): array {
		return array_filter($this->adapters, fn($adapter) => $adapter->is_active());
	}

	/**
	 * Process a form submission (called by adapters)
	 *
	 * @param string $plugin Plugin key
	 * @param int|string $form_id Form ID
	 * @param string $form_name Form name
	 * @param array $field_values Extracted field values
	 * @param array $mapping Mapping configuration
	 */
	public function process_submission(
		string $plugin,
		$form_id,
		string $form_name,
		array $field_values,
		array $mapping
	): void {
		// ===== LICENSE VALIDATION =====
		// Block submissions if no valid license
		$license_check = $this->license->check_license();
		if (!$license_check['valid']) {
			$this->log_submission($plugin, $form_id, $form_name, $field_values, 'blocked', [
				'error' => 'No valid license key. Form submission blocked.',
				'license_error' => $license_check['error'] ?? 'License not active',
				'license_code' => $license_check['code'] ?? 'no_license',
			]);
			return;
		}

		// Determine location (conditional or default)
		$location_id = $this->mappings->get_location_for_submission($plugin, $form_id, $field_values);

		if (empty($location_id)) {
			$this->log_submission($plugin, $form_id, $form_name, $field_values, 'blocked', [
				'error' => 'No location configured for this form.',
			]);
			return;
		}

		// ===== REGISTERED HOMES CHECK =====
		// Only allow submissions to homes that are registered with the license
		// and within the paid seat limit
		if (!$this->is_home_registered($location_id)) {
			$this->log_submission($plugin, $form_id, $form_name, $field_values, 'blocked', [
				'error' => 'Home is not registered with your license or exceeds your paid seat limit. Please add this home in settings, remove excess homes, or upgrade your subscription.',
				'location_id' => $location_id,
			]);
			return;
		}

		// Get location name for logging
		$location_name = $this->get_location_name($location_id);

		// Map form fields to CareHQ fields
		$mapped_data = $this->map_fields($field_values, $mapping['fields'] ?? []);

		if (empty($mapped_data)) {
			$this->log_submission($plugin, $form_id, $form_name, $field_values, 'error', [
				'error' => 'No fields could be mapped. Check field mappings.',
				'available_keys' => array_keys($field_values),
				'mapping' => $mapping['fields'] ?? [],
			], $location_id, $location_name);
			return;
		}

		// Add location to payload
		$mapped_data['location'] = $location_id;

		// Add sales channel - REQUIRED by CareHQ API
		$sales_channel = $this->get_sales_channel();
		
		// sales_channel is required by CareHQ API - block submission if not configured
		if (empty($sales_channel)) {
			$this->log_submission($plugin, $form_id, $form_name, $field_values, 'error', [
				'error' => 'Sales Channel is not configured. Please configure a Sales Channel in CareHQ API settings.',
			], $location_id, $location_name);
			return;
		}
		$mapped_data['sales_channel'] = sanitize_text_field($sales_channel);

		// Validate and format phone number (GB only) if provided
		// Note: Required field validation is left to the form plugin
		if (!empty($mapped_data['phone'])) {
			$phone_result = $this->phone_validator->validate_and_format($mapped_data['phone']);
			
			if (!$phone_result['valid']) {
				// Invalid phone - remove from payload
				$this->log_debug("Invalid phone number removed from submission", [
					'submitted_phone' => $mapped_data['phone'],
					'error' => $phone_result['error'] ?? 'Not a valid GB phone number',
				]);
				unset($mapped_data['phone']);
			} else {
				// Use the formatted E.164 phone number
				$mapped_data['phone'] = $phone_result['formatted'];
			}
		}

		// Extract and add UTM parameters if present
		$mapped_data = $this->add_utm_data($mapped_data, $field_values);

		// Add untrusted_source flag from settings (so it appears in logs)
		$carehq_settings = \FormConnector\Integration\CareHQ\Settings::get_untrusted_source();
		$mapped_data['untrusted_source'] = $carehq_settings;

		// Duplicate detection: block re-submission of the same email within the
		// configured window. Skipped when the window is 0 (feature disabled).
		$fc_settings = \FormConnector\Core\Settings\Settings::get_instance();
		$dup_window  = (int) $fc_settings->get('duplicate_window_hours', 0);
		if ($dup_window > 0 && !empty($mapped_data['email'])) {
			if ($this->logs->has_recent_submission_by_email((string) $mapped_data['email'], $dup_window)) {
				$this->log_submission(
					$plugin,
					$form_id,
					$form_name,
					$field_values,
					'duplicate',
					[
						'message' => sprintf(
							'Duplicate submission blocked. The same email address was submitted successfully within the last %d hour(s).',
							$dup_window
						),
					],
					$location_id,
					$location_name
				);
				return;
			}
		}

		// Send to CareHQ
		$result = $this->api->send_care_enquiry($mapped_data);

		// Build field labels mapping (field_id => carehq_field_name)
		$field_labels = [];
		$field_mapping = is_object($mapping['fields'] ?? null) ? (array) $mapping['fields'] : ($mapping['fields'] ?? []);
		foreach ($field_mapping as $carehq_field => $form_field_id) {
			if (!empty($form_field_id)) {
				$field_labels[$form_field_id] = $this->format_field_label($carehq_field);
			}
		}

		// Combine original field values with mapped data for complete logging
		$log_data = [
			'original_fields' => $field_values,
			'field_labels' => $field_labels,
			'mapped_to_carehq' => $mapped_data,
		];

		// Log the result
		if (is_wp_error($result)) {
			$log_id = $this->log_submission(
				$plugin,
				$form_id,
				$form_name,
				$log_data,
				'error',
				$result,
				$location_id,
				$location_name
			);

			// Enqueue for retry
			$retry_queue = RetryQueue::get_instance();
			$retry_queue->enqueue(
				$mapped_data,
				$plugin,
				(string) $form_id,
				$form_name,
				$location_id,
				$location_name,
				$result->get_error_message(),
				$log_id ?: 0
			);

			// Notify about failure
			$notifier = EmailNotifier::get_instance();
			$notifier->on_submission_failed([
				'form_name'     => $form_name,
				'form_plugin'   => $plugin,
				'error'         => $result->get_error_message(),
				'location_id'   => $location_id,
				'location_name' => $location_name,
			]);
		} else {
			$this->log_submission(
				$plugin,
				$form_id,
				$form_name,
				$log_data,
				'success',
				$result,
				$location_id,
				$location_name
			);

			// Reset failure counter on success
			EmailNotifier::get_instance()->on_submission_success();

			// Track submission in analytics API (GDPR-friendly, non-blocking)
			$analytics_id = $plugin . '-' . $form_id;
			$tracker = SubmissionTracker::get_instance();
			$analytics_result = $tracker->track($analytics_id, $form_name, $location_id);
			if (!$analytics_result['success'] && ($analytics_result['error'] ?? '') !== 'disabled') {
				$this->log_debug('Analytics tracking failed', [
					'error' => $analytics_result['message'] ?? 'Unknown error',
					'form_id' => $analytics_id,
					'home_id' => $location_id,
				]);
			}

			// Record attribution data for campaign analytics
			$this->record_attribution($plugin, $form_id, $form_name, $field_values, $mapped_data, $location_id, $location_name, $result);
		}
	}

	/**
	 * Map form fields to CareHQ fields
	 *
	 * @param array $field_values Form field values
	 * @param array|object $field_mapping Field mapping configuration
	 * @return array Mapped CareHQ data
	 */
	public function map_fields(array $field_values, $field_mapping): array {
		$mapped = [];

		// Handle empty or object mapping
		if (empty($field_mapping)) {
			return $mapped;
		}

		// Convert object to array if needed
		if (is_object($field_mapping)) {
			$field_mapping = (array) $field_mapping;
		}

		foreach ($field_mapping as $carehq_field => $form_field_id) {
			if (empty($form_field_id)) {
				continue;
			}

			if (isset($field_values[$form_field_id])) {
				$value = $field_values[$form_field_id];
				
				// Clean up the value
				$value = is_array($value) ? implode(', ', $value) : trim((string) $value);
				
				if (!empty($value)) {
					$mapped[$carehq_field] = $value;
				}
			}
		}

		return $mapped;
	}

	/**
	 * Add UTM tracking data to payload
	 *
	 * @param array $data Payload data
	 * @param array $field_values Form field values
	 * @return array Updated payload
	 */
	private function add_utm_data(array $data, array $field_values): array {
		$data = $this->utm_reader->apply_to_payload($data, $field_values);

		// Log UTM data state for diagnostics
		$utm_values = $this->utm_reader->extract_utm_values($field_values);
		$debug_info = $this->utm_reader->get_debug_info($utm_values);
		$debug_info['referrer_other'] = $data['referrer_other'] ?? '';
		$this->log_debug('UTM data extraction', $debug_info);

		return $data;
	}

	/**
	 * Record attribution data for a successful submission.
	 *
	 * @param string     $plugin        Plugin key
	 * @param int|string $form_id       Form ID
	 * @param string     $form_name     Form name
	 * @param array      $field_values  Raw form field values
	 * @param array      $mapped_data   CareHQ mapped data
	 * @param string     $location_id   Location ID
	 * @param string     $location_name Location name
	 * @param mixed      $result        CareHQ API response
	 */
	private function record_attribution(
		string $plugin,
		$form_id,
		string $form_name,
		array $field_values,
		array $mapped_data,
		string $location_id,
		string $location_name,
		$result
	): void {
		$utm_log = $this->utm_reader->get_log_data($mapped_data);

		// Extract click IDs from form fields or cookies (sanitized)
		$gclid  = sanitize_text_field(wp_unslash($field_values['gclid'] ?? $_COOKIE['_gcl_aw'] ?? ''));
		$fbclid = sanitize_text_field(wp_unslash($field_values['fbclid'] ?? $_COOKIE['_fbc'] ?? ''));
		$msclid = sanitize_text_field(wp_unslash($field_values['msclkid'] ?? $_COOKIE['_uetmsclkid'] ?? ''));

		// Try to extract CareHQ contact ID from response
		$contact_id = '';
		if (is_array($result)) {
			$contact_id = (string) ($result['contact_id'] ?? $result['_id'] ?? '');
		}

		\FormConnector\Features\Attribution\AttributionTracker::get_instance()->record([
			'form_plugin'       => $plugin,
			'form_id'           => (string) $form_id,
			'form_name'         => $form_name,
			'utm_source'        => $utm_log['utm_source'] ?? '',
			'utm_medium'        => $utm_log['utm_medium'] ?? '',
			'utm_campaign'      => $utm_log['utm_campaign'] ?? '',
			'utm_term'          => $utm_log['utm_term'] ?? '',
			'utm_content'       => $utm_log['utm_content'] ?? '',
			'referrer'          => $utm_log['referrer_other'] ?? '',
			'landing_page'      => $utm_log['landing_page'] ?? '',
			'gclid'             => $gclid,
			'fbclid'            => $fbclid,
			'msclkid'           => $msclid,
			'location_id'       => $location_id,
			'location_name'     => $location_name,
			'carehq_contact_id' => $contact_id,
			'lead_status'       => 'new',
		]);
	}

	/**
	 * Get location name from ID
	 *
	 * @param string $location_id Location ID
	 * @return string Location name
	 */
	private function get_location_name(string $location_id): string {
		$homes = new Homes();
		$result = $homes->get_homes();

		if (!isset($result['success']) || !$result['success']) {
			return $location_id;
		}

		$homes_list = $result['data']['homes'] ?? [];
		foreach ($homes_list as $home) {
			$home_id = $home['homeId'] ?? $home['_id'] ?? '';
			if ($home_id === $location_id) {
				return $home['name'] ?? $location_id;
			}
		}

		return $location_id;
	}

	/**
	 * Log a submission
	 *
	 * @param string $plugin Plugin key
	 * @param int|string $form_id Form ID
	 * @param string $form_name Form name
	 * @param array $submitted_data Submitted data
	 * @param string $status Status (success, error, blocked)
	 * @param mixed $response Response or error
	 * @param string $location_id Location ID
	 * @param string $location_name Location name
	 * @return int|false The log entry ID or false on failure
	 */
	private function log_submission(
		string $plugin,
		$form_id,
		string $form_name,
		array $submitted_data,
		string $status,
		$response,
		string $location_id = '',
		string $location_name = ''
	): int|false {
		$mapped = $submitted_data['mapped_to_carehq'] ?? [];
		$utm_log_data = $this->utm_reader->get_log_data($mapped);

		return $this->logs->log_submission(array_merge([
			'form_id' => (string) $form_id,
			'form_name' => $form_name,
			'form_plugin' => $plugin,
			'submitted_data' => $submitted_data,
			'status' => $status,
			'response' => $response,
			'location_id' => $location_id,
			'location_name' => $location_name,
		], $utm_log_data));
	}

	/**
	 * Format a CareHQ field key into a human-readable label
	 *
	 * @param string $field_key The CareHQ field key (e.g., 'first_name', 'care_requirements')
	 * @return string Human-readable label (e.g., 'First Name', 'Care Requirements')
	 */
	private function format_field_label(string $field_key): string {
		// Check if we have a defined label for this field
		if (isset(self::CAREHQ_FIELDS[$field_key])) {
			return self::CAREHQ_FIELDS[$field_key];
		}

		// Otherwise, convert snake_case to Title Case
		$label = str_replace(['_', '-'], ' ', $field_key);
		return ucwords($label);
	}

	/**
	 * Log debug message
	 *
	 * @param string $message Message
	 * @param array $context Context
	 */
	private function log_debug(string $message, array $context = []): void {
		if (defined('WP_DEBUG') && WP_DEBUG) {
			error_log(sprintf(
				'[FormConnector Handler] %s | %s',
				$message,
				wp_json_encode($context)
			));
		}
	}

	/**
	 * Get sales channel ID with caching
	 * 
	 * Caches the value to avoid repeated database lookups during a single request.
	 *
	 * @return string Sales channel ID or empty string if not configured
	 */
	private function get_sales_channel(): string {
		// Return cached value if available
		if ($this->sales_channel_cache !== null) {
			return $this->sales_channel_cache;
		}

		// Try CareHQ Settings first
		$carehq_settings = new \FormConnector\Integration\CareHQ\Settings();
		$carehq_config = $carehq_settings->get_settings();
		$sales_channel = $carehq_config['sales_channel_id'] ?? '';
		
		// Fallback to legacy option keys
		if (empty($sales_channel)) {
			$sales_channel = get_option('fc_carehq_sales_channel_id', '');
		}
		if (empty($sales_channel)) {
			$sales_channel = get_option('cfc_sales_channel_id', '');
		}

		// Cache for this request
		$this->sales_channel_cache = $sales_channel;

		return $sales_channel;
	}

	/**
	 * Check if a home/location is registered with the user's license
	 * AND is within the paid seat limit.
	 * 
	 * This prevents submissions to locations that aren't covered by the subscription.
	 * After a trial ends, only the first N homes (matching paid seats) can receive submissions.
	 *
	 * @param string $location_id The CareHQ location ID to check
	 * @return bool True if home is registered and within seat limit, false otherwise
	 */
	private function is_home_registered(string $location_id): bool {
		$homes_manager = new Homes();
		$homes_result = $homes_manager->get_homes(false); // Use cached data

		// If we can't fetch homes, allow the submission (fail open for now)
		// This prevents blocking all submissions if the homes API is temporarily unavailable
		if (!$homes_result['success']) {
			$this->log_debug('Could not verify registered homes, allowing submission', [
				'location_id' => $location_id,
				'error' => $homes_result['error'] ?? 'Unknown error',
			]);
			return true;
		}

		$registered_homes = $homes_result['data']['homes'] ?? [];

		// If no homes are registered yet, block all submissions
		if (empty($registered_homes)) {
			$this->log_debug('No homes registered with license', [
				'location_id' => $location_id,
			]);
			return false;
		}

		// Check if this location is in the registered homes
		$is_registered = false;
		foreach ($registered_homes as $home) {
			if (($home['homeId'] ?? '') === $location_id) {
				$is_registered = true;
				break;
			}
		}

		if (!$is_registered) {
			$this->log_debug('Location not found in registered homes', [
				'location_id' => $location_id,
				'registered_home_ids' => array_column($registered_homes, 'homeId'),
			]);
			return false;
		}

		// Check seat limits using the priority-aware method
		// This respects user-defined home priority order for determining
		// which homes are active when over the seat limit
		if (!$homes_manager->is_home_within_seat_limit($location_id)) {
			$seat_info = $homes_manager->get_active_home_ids();
			$this->log_debug('Home exceeds paid seat limit - submission blocked', [
				'location_id' => $location_id,
				'total_seats' => $seat_info['total_seats'],
				'total_homes' => $seat_info['used_seats'],
				'active_home_ids' => $seat_info['active_home_ids'],
			]);
			return false;
		}

		return true;
	}

	/**
	 * Get CareHQ standard fields (all fields, flat)
	 *
	 * @return array Field definitions
	 */
	public static function get_carehq_fields(): array {
		return self::CAREHQ_FIELDS;
	}
}
