<?php

namespace Limb_Chatbot\Includes\Services\Export;

use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Factories\Chat_Export_Formatter_Factory;
use Limb_Chatbot\Includes\Repositories\Chat_Repository;

/**
 * Class Chat_Export_Service
 *
 * Handles the full chat export pipeline:
 * fetches chats, resolves chatbot, builds structured data,
 * formats via the appropriate strategy, and sends the download response.
 *
 * Uses direct output (flush buffers → headers → echo → wp_die) following
 * the same pattern as WP core's export_wp().
 *
 * @package Limb_Chatbot\Includes\Services\Export
 * @since 2.0.0
 */
class Chat_Export_Service {

	const SOURCE_PUBLISHED = 'Published Chatbot';
	const SOURCE_PREVIEW   = 'Preview';

	/**
	 * @var Chat_Repository
	 */
	private Chat_Repository $chat_repository;

	/**
	 * @var Chat_Export_Formatter_Factory
	 */
	private Chat_Export_Formatter_Factory $formatter_factory;

	/**
	 * Chat_Export_Service constructor.
	 *
	 * @param Chat_Repository|null              $chat_repository   Chat repository instance.
	 * @param Chat_Export_Formatter_Factory|null $formatter_factory Formatter factory instance.
	 */
	public function __construct(
		?Chat_Repository $chat_repository = null,
		?Chat_Export_Formatter_Factory $formatter_factory = null
	) {
		$this->chat_repository   = $chat_repository ?? new Chat_Repository();
		$this->formatter_factory = $formatter_factory ?? new Chat_Export_Formatter_Factory();
	}

	/**
	 * Export chats based on the given params and stream the file to the client.
	 *
	 * @param array $params Sanitised request params (from fill_personalized_params).
	 *
	 * @return void Terminates with file output.
	 */
	public function export( array $params ): void {
		$params['per_page'] = -1;

		$collection = $this->chat_repository->get_items( $params );
		$chats      = $collection->is_empty() ? [] : $collection->get();
		$chatbot    = $this->resolve_chatbot( $params, $chats );
		$formatter  = $this->formatter_factory->make( $params['format'] ?? 'json' );

		$export_data = $this->build_export_data( $chatbot, $chats, $params );
		$content     = $formatter->format( $export_data );
		$filename    = $this->generate_filename( $chatbot, $params, $formatter->get_file_extension() );

		$this->send_download( $content, $formatter->get_content_type(), $filename );
	}

	/**
	 * Export a single chat by its data object and stream the file to the client.
	 *
	 * @param Chat   $chat   The chat to export.
	 * @param string $format The export format (json, csv, pdf).
	 *
	 * @return void Terminates with file output.
	 * @since 2.0.0
	 */
	public function export_single_chat( Chat $chat, string $format = 'json' ): void {
		$chatbot   = $chat->get_chatbot();
		$chatbot   = $chatbot instanceof Chatbot ? $chatbot : Chatbot::make();
		$formatter = $this->formatter_factory->make( $format );

		$export_data = $this->build_export_data( $chatbot, [ $chat ], [] );
		$content     = $formatter->format( $export_data );
		$filename    = $this->generate_single_chat_filename( $chat, $formatter->get_file_extension() );

		$this->send_download( $content, $formatter->get_content_type(), $filename );
	}

	/**
	 * Generate the export filename for a single chat.
	 *
	 * @param Chat   $chat      The chat.
	 * @param string $extension File extension.
	 *
	 * @return string
	 * @since 2.0.0
	 */
	private function generate_single_chat_filename( Chat $chat, string $extension ): string {
		$uuid_short = sanitize_file_name( substr( $chat->get_uuid(), 0, 8 ) );
		$date       = current_time( 'Y-m-d' );

		return sprintf( 'chat_%s_%s.%s', $uuid_short, $date, $extension );
	}

	/**
	 * Resolve the Chatbot from params or the first fetched chat.
	 *
	 * @param array  $params Request params.
	 * @param Chat[] $chats  Fetched chats.
	 *
	 * @return Chatbot
	 */
	private function resolve_chatbot( array $params, array $chats ): Chatbot {
		$uuids = $params['chatbot_uuid'] ?? [];
		if ( ! empty( $uuids ) ) {
			$uuid    = is_array( $uuids ) ? reset( $uuids ) : $uuids;
			$chatbot = Chatbot::find_by_uuid( sanitize_text_field( $uuid ) );
			if ( $chatbot instanceof Chatbot ) {
				return $chatbot;
			}
		}

		$first_chat = reset( $chats );
		if ( $first_chat instanceof Chat ) {
			$chatbot = $first_chat->get_chatbot();
			if ( $chatbot instanceof Chatbot ) {
				return $chatbot;
			}
		}

		return Chatbot::make();
	}

	/**
	 * Build structured export data consumed by formatters.
	 *
	 * @param Chatbot $chatbot The chatbot.
	 * @param Chat[]  $chats   Chats to export.
	 * @param array   $params  Request params.
	 *
	 * @return array
	 */
	private function build_export_data( Chatbot $chatbot, array $chats, array $params ): array {
		$conversations = [];
		foreach ( $chats as $chat ) {
			if ( ! $chat instanceof Chat ) {
				continue;
			}

			$messages     = $this->fetch_messages( $chat );
			$last_message = ! empty( $messages ) ? end( $messages ) : null;

			$conversations[] = [
				'chat_uuid'       => $chat->get_uuid(),
				'chat_name'       => $chat->get_name() ?? '',
				'created_at'      => $chat->get_created_at(),
				'updated_at'      => $chat->get_updated_at(),
				'last_message_at' => $last_message instanceof Message ? $last_message->get_created_at() : $chat->get_updated_at(),
				'source'          => $this->resolve_chat_source( $chat ),
				'messages'        => $messages,
			];
		}

		return [
			'chatbot_id'    => $chatbot->uuid ?? '',
			'chatbot_name'  => $chatbot->get_parameter( 'title' ) ?? __( 'Default Chatbot', 'limb-chatbot' ),
			'start_date'    => $params['start_date'] ?? $params['created_at>='] ?? '',
			'end_date'      => $params['end_date'] ?? $params['created_at<='] ?? '',
			'conversations' => $conversations,
		];
	}

	/**
	 * Resolve source label for a chat.
	 *
	 * @param Chat $chat The chat.
	 *
	 * @return string
	 */
	private function resolve_chat_source( Chat $chat ): string {
		$source_meta = $chat->get_meta( 'source' );
		if ( ! empty( $source_meta ) ) {
			return sanitize_text_field( $source_meta );
		}

		if ( ! empty( $chat->get_meta( 'preview' ) ) ) {
			return self::SOURCE_PREVIEW;
		}

		return self::SOURCE_PUBLISHED;
	}

	/**
	 * Fetch messages for a chat ordered chronologically.
	 *
	 * @param Chat $chat The chat.
	 *
	 * @return Message[]
	 */
	private function fetch_messages( Chat $chat ): array {
		$collection = Message::where( [ 'chat_uuid' => $chat->get_uuid() ], -1, -1, 'id', 'ASC' );

		return $collection->is_empty() ? [] : $collection->get();
	}

	/**
	 * Generate the export filename.
	 *
	 * @param Chatbot $chatbot   The chatbot.
	 * @param array   $params    Request params.
	 * @param string  $extension File extension.
	 *
	 * @return string
	 */
	private function generate_filename( Chatbot $chatbot, array $params, string $extension ): string {
		$id    = sanitize_file_name( $chatbot->uuid ?? 'default' );
		$start = ! empty( $params['created_at>='] ) ? sanitize_file_name( substr( $params['created_at>='], 0, 10 ) ) : current_time( 'Y-m-d' );
		$end   = ! empty( $params['created_at<='] ) ? sanitize_file_name( substr( $params['created_at<='], 0, 10 ) ) : current_time( 'Y-m-d' );

		return sprintf( 'chats_%s_%s~%s.%s', $id, $start, $end, $extension );
	}

	/**
	 * Flush all active output buffers before writing download content.
	 *
	 * @since 1.0.15
	 * @return void
	 */
	private function flush_output_buffers(): void {
		while ( ob_get_level() > 0 ) {
			ob_end_clean();
		}
	}

	/**
	 * Send the file download response and terminate.
	 *
	 * @since 2.0.0
	 *
	 * @param string $content      Raw file content (JSON, CSV, or PDF binary).
	 * @param string $content_type MIME type for the Content-Type header.
	 * @param string $filename     Download filename (will be sanitized).
	 *
	 * @return void Terminates request via wp_die().
	 */
	private function send_download( string $content, string $content_type, string $filename ): void {
		// Drain WP/plugin output buffers before sending headers.
		$this->flush_output_buffers();

		// WP-native no-cache headers (replaces manual Cache-Control/Pragma/Expires).
		nocache_headers();

		$safe_filename = sanitize_file_name( $filename );

		header( 'Content-Type: ' . sanitize_mime_type( $content_type ) );
		header( 'Content-Disposition: attachment; filename="' . $safe_filename . '"' );
		header( 'Content-Length: ' . strlen( $content ) );

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Raw file body; escaping would corrupt binary/structured content.
		echo $content;

		// Clean termination: fires shutdown hooks, respects active die handler.
		wp_die( '', '', array( 'response' => 200 ) );
	}
}

