<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Lead;
use Limb_Chatbot\Includes\Repositories\Lead_Repository;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Lead_Filters_Service;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Controller for managing Leads via REST API.
 *
 * Provides endpoints for retrieving lead records with advanced filtering and search:
 * - POST /leads - List all leads with filtering, search, and pagination
 * - DELETE /leads?id[]=1&id[]=2 - Batch delete leads (and values)
 * - GET /leads/{id} - Get a single lead by ID
 * - PUT /leads/{id} - Update lead status, qualification, metadata, and values
 *
 * @package Limb_Chatbot\Includes\Api\V1\Controllers
 * @since 1.0.13
 */
class Leads_Controller extends Rest_Controller {

	/**
	 * REST route base.
	 *
	 * @var string
	 * @since 1.0.13
	 */
	protected $rest_base = 'leads';

	/**
	 * Lead repository instance.
	 *
	 * @var Lead_Repository
	 * @since 1.0.13
	 */
	protected Lead_Repository $repository;
	/**
	 * Leads_Controller constructor.
	 *
	 * @since 1.0.13
	 */
	public function __construct() {
		$this->repository = new Lead_Repository();
	}

	/**
	 * Registers REST routes for leads.
	 *
	 * @return void
	 * @since 1.0.13
	 */
	public function register_routes() {
		// POST /leads - List all leads with filtering and search
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'get_items' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => $this->get_collection_params(),
			),
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'batch_delete' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'type'              => 'array',
						'required'          => true,
						'sanitize_callback' => array( $this, 'sanitize_lead_ids' ),
						'validate_callback' => array( $this, 'validate_lead_ids' ),
					),
				),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );

		// GET /leads/{id} - Get a single lead
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $value ) {
							return ! empty( Lead::find( $value ) );
						},
					),
				),
			),
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $value ) {
							return ! empty( Lead::find( $value ) );
						},
					),
					'status' => array(
						'required' => false,
						'type'     => 'string',
						'enum'     => array( Lead::STATUS_NEW, Lead::STATUS_ARCHIVED ),
					),
					'is_qualified' => array(
						'required' => false,
						'type'     => array( 'boolean', 'integer' ),
					),
					'metadata' => array(
						'required' => false,
						'type'     => 'object',
					),
					'lead_values' => array(
						'required' => false,
						'type'     => 'object',
					),
				),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
	}

	/**
	 * Returns query parameters for the collection endpoint.
	 *
	 * These parameters allow filtering, searching, and sorting of lead results.
	 *
	 * @return array Array of query parameters accepted by the collection route.
	 * @since 1.0.13
	 */
	public function get_collection_params() {
		return array_merge( parent::get_collection_params(), array(
			'orderby'          => array(
				'description'       => __( 'Sort the collection by attribute.', 'limb-chatbot' ),
				'type'              => 'string',
				'default'           => 'created_at',
				'enum'              => array( 'id', 'created_at', 'source_type', 'status', 'is_qualified', 'source_title' ),
				'validate_callback' => 'rest_validate_request_arg',
			),
			'search'           => array(
				'description' => __( 'Search leads by source title, source ID, or UUIDs.', 'limb-chatbot' ),
				'type'        => 'string',
			),
			'search_fields'    => array(
				'description' => __( 'Fields to search in (e.g., source_title, source, chatbot_user_uuid, chatbot_uuid).', 'limb-chatbot' ),
				'type'        => 'array',
				'default'     => array( 'source_title', 'source' ),
			),
			'source_type'      => array(
				'description'       => __( 'Filter by source type.', 'limb-chatbot' ),
				'type'              => 'string',
				'enum'              => array( Lead::SOURCE_TYPE_ACTION, Lead::SOURCE_TYPE_WIDGET ),
				'sanitize_callback' => 'sanitize_text_field',
			),
			'source'           => array(
				'description'       => __( 'Filter by source ID (action ID or widget ID).', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'status'           => array(
				'description'       => __( 'Filter by status.', 'limb-chatbot' ),
				'type'              => 'string',
				'enum'              => array( Lead::STATUS_NEW, Lead::STATUS_ARCHIVED ),
				'sanitize_callback' => 'sanitize_text_field',
			),
			'is_qualified'    => array(
				'description'       => __( 'Filter by qualification status.', 'limb-chatbot' ),
				'type'              => 'integer',
				'enum'              => array( 0, 1 ),
				'sanitize_callback' => 'absint',
			),
			'chatbot_user_uuid' => array(
				'description'       => __( 'Filter by chatbot user UUID.', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'chatbot_uuid'     => array(
				'description'       => __( 'Filter by chatbot UUID.', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'start_date'       => array(
				'description'       => __( 'Filter leads created on or after this date (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS).', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'end_date'         => array(
				'description'       => __( 'Filter leads created on or before this date (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS).', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'created_at>='     => array(
				'description'       => __( 'Filter leads created on or after this datetime.', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'created_at<='     => array(
				'description'       => __( 'Filter leads created on or before this datetime.', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'value_search'     => array(
				'description'       => __( 'General search across all lead field values (searches value_string, value_number, value_date).', 'limb-chatbot' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			),
			'lead_values_include' => array(
				'description'       => __( 'Lead field keys to include in included.lead_values.', 'limb-chatbot' ),
				'type'              => 'array',
				'sanitize_callback' => function ( $value ) {
					if ( ! is_array( $value ) ) {
						return array();
					}

					return array_map( 'sanitize_key', $value );
				},
			),
			'include'          => array(
				'description' => __( 'Include extra data with leads (e.g., values).', 'limb-chatbot' ),
				'type'        => 'array',
			),
			'filters'          => array(
				'description'       => __( 'Structured filters for lead fields and lead values.', 'limb-chatbot' ),
				'type'              => 'array',
				'validate_callback' => function ( $value ) {
					$service = new Lead_Filters_Service();

					return $service->validate_filters( $value );
				},
			),
		) );
	}

	/**
	 * Returns a collection of lead items.
	 *
	 * Supports advanced filtering, search, and pagination.
	 *
	 * @param  WP_REST_Request  $request  REST request object.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.13
	 */
	public function get_items( $request ) {
		try {
			$query_params = $request->get_query_params();
			$body_params  = $request->get_json_params();

			if ( is_array( $body_params ) && array_key_exists( 'filters', $body_params ) ) {
				$query_params['filters'] = $body_params['filters'];
			}

			$leads = $this->repository->get_items( $query_params );
			$items = $this->prepare_collection( $leads, $request );

			return rest_ensure_response( $items ?? [] );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Prepares a collection of lead items for REST response.
	 *
	 * Ensures each lead includes chatbot_user and lead_values with lead_field.
	 *
	 * @param  Collection  $collection  The collection of leads.
	 * @param  WP_REST_Request  $request  The REST request object.
	 *
	 * @return Collection Prepared collection with full data.
	 * @since 1.0.13
	 */
	public function prepare_collection( $collection, $request ) {
		$collection = parent::prepare_collection( $collection, $request );

		if ( $collection->is_empty() ) {
			return $collection;
		}

		// Prepare each item with full data
		foreach ( $collection->get() as $lead ) {
			$this->prepare_item( $lead, $request );
		}

		return $collection;
	}

	/**
	 * Prepares a single lead item for REST response.
	 *
	 * Always includes chatbot_user and lead_values (with lead_field) for full data access.
	 *
	 * @param  mixed  $item  The item to prepare (Lead instance).
	 * @param  WP_REST_Request  $request  The full REST request object.
	 *
	 * @return Lead|WP_Error The prepared item or WP_Error if invalid.
	 * @throws \Exception
	 * @since 1.0.13
	 */
	public function prepare_item( $item, $request ) {
		if ( is_a( $item, WP_Error::class ) ) {
			return $item;
		}
		if ( ! is_a( $item, Lead::class ) ) {
			throw new \Exception( __( 'Unknown item type.', 'limb-chatbot' ) );
		}

		// Always include chatbot_user
		if ( method_exists( $item, 'chatbot_user' ) ) {
			$item->included['chatbot_user'] = $item->chatbot_user();
		}

		// Always include lead_values with lead_field
		if ( method_exists( $item, 'lead_values' ) ) {
			$values = $item->lead_values();
			$values_with_fields = [];
			
			// Get field keys to include.
			$include_field_keys = $request->get_param( 'lead_values_include' );
			$include_field_keys = ! empty( $include_field_keys ) && is_array( $include_field_keys ) 
				? array_map( 'sanitize_key', $include_field_keys ) 
				: null;
			
			foreach ( $values as $value ) {
				// Get the field to check its field_key
				$field = null;
				if ( method_exists( $value, 'lead_field' ) ) {
					$field = $value->lead_field();
				}
				
				// If lead_values_include is specified, filter by field_key
				if ( $include_field_keys !== null && $field ) {
					if ( ! in_array( $field->field_key, $include_field_keys, true ) ) {
						continue; // Skip this value if not in include list
					}
				}
				
				$value_data = [
					'id'           => $value->id,
					'lead_id'      => $value->lead_id,
					'field_id'     => $value->field_id,
					'value_string' => $value->value_string,
					'value_number' => $value->value_number,
					'value_date'   => $value->value_date,
					'value'        => $value->get_value(),
				];
				if (
					$field
					&& $field->data_type === \Limb_Chatbot\Includes\Data_Objects\Lead_Field::DATA_TYPE_ATTACHMENT
					&& is_string( $value_data['value_string'] )
				) {
					$decoded = json_decode( $value_data['value_string'], true );
					if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
						$value_data['attachments'] = $decoded;
					}
				}
				
				// Include lead_field object
				if ( $field ) {
					$value_data['lead_field'] = [
						'id'        => $field->id,
						'field_key' => $field->field_key,
						'label'     => $field->label,
						'data_type' => $field->data_type,
					];
				}
				
				$values_with_fields[] = $value_data;
			}
			
			$item->included['lead_values'] = $values_with_fields;
		}

		// Handle additional included relations
		$include = $request->get_param( 'include' );
		if ( ! empty( $include ) && is_array( $include ) ) {
			foreach ( $include as $relation ) {
				if ( method_exists( $item, $relation ) && ! isset( $item->included[ $relation ] ) ) {
					$item->included[ $relation ] = $item->{$relation}();
				}
			}
		}

		return $item;
	}

	/**
	 * Returns a single lead item by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request object.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.13
	 */
	public function get_item( $request ) {
		try {
			$lead = $this->repository->get_item( $request->get_param( 'id' ) );

			return rest_ensure_response( $this->prepare_item( $lead, $request ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Batch delete leads.
	 *
	 * Accepts query payload: /leads?id[]=1&id[]=2
	 *
	 * @param  WP_REST_Request  $request
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.18
	 */
	public function batch_delete( $request ) {
		try {
			$ids     = $request->get_param( 'id' );
			$deleted = $this->repository->batch_delete( is_array( $ids ) ? $ids : [] );

			return rest_ensure_response( array( 'deleted' => $deleted ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Sanitize lead IDs payload.
	 *
	 * @param  mixed  $value
	 *
	 * @return int[]
	 * @since 1.0.18
	 */
	public function sanitize_lead_ids( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}

		$ids = array_map( 'absint', $value );
		$ids = array_filter( array_unique( $ids ) );

		return array_values( $ids );
	}

	/**
	 * Validate lead IDs payload.
	 *
	 * @param  mixed  $value
	 * @param  mixed  ...$_args
	 *
	 * @return bool
	 * @since 1.0.18
	 */
	public function validate_lead_ids( $value, ...$_args ): bool {
		if ( ! is_array( $value ) || empty( $value ) ) {
			return false;
		}

		foreach ( $value as $id ) {
			if ( ! is_numeric( $id ) || absint( $id ) <= 0 ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Updates an existing lead and its values.
	 *
	 * Supported payload keys:
	 * - status
	 * - is_qualified
	 * - metadata.notes
	 * - lead_values[field_key] => typed value
	 *
	 * @param  WP_REST_Request  $request  REST request with update payload.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.18
	 */
	public function update_item( $request ) {
		try {
			$payload = $request->get_json_params();
			$payload = is_array( $payload ) ? $payload : [];
			$lead    = $this->repository->update( (int) $request->get_param( 'id' ), $payload );

			return rest_ensure_response( $this->prepare_item( $lead, $request ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Returns the JSON schema for a lead item.
	 *
	 * Defines structure and validation rules for the lead resource.
	 *
	 * @return array JSON schema array.
	 * @since 1.0.13
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->schema;
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'lead',
			'type'       => 'object',
			'properties' => array(
				'id'                 => array(
					'description' => __( 'Unique identifier for the lead.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'chatbot_user_uuid'  => array(
					'description' => __( 'Chatbot user UUID who submitted the lead.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'chatbot_uuid'       => array(
					'description' => __( 'Chatbot UUID (optional).', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'source_type'        => array(
					'description' => __( 'Source type (action or widget).', 'limb-chatbot' ),
					'type'        => 'string',
					'enum'        => array( Lead::SOURCE_TYPE_ACTION, Lead::SOURCE_TYPE_WIDGET ),
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'source'             => array(
					'description' => __( 'Source ID (action ID or widget ID).', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'source_title'       => array(
					'description' => __( 'Source title (action title or widget title).', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'created_at'         => array(
					'description' => __( 'When the lead was captured.', 'limb-chatbot' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'status'             => array(
					'description' => __( 'Lead status.', 'limb-chatbot' ),
					'type'        => 'string',
					'enum'        => array( Lead::STATUS_NEW, Lead::STATUS_ARCHIVED ),
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'is_qualified'       => array(
					'description' => __( 'Whether the lead is qualified.', 'limb-chatbot' ),
					'type'        => 'integer',
					'enum'        => array( 0, 1 ),
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'include'            => array(
					'description' => __( 'Include extra data with lead (e.g., values).', 'limb-chatbot' ),
					'type'        => 'array',
					'context'     => array( 'view' ),
				),
			),
		);

		return $this->schema;
	}
}
