<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Exception;
use Limb_Chatbot\Includes\Database_Strategies\WP_Options;
use Limb_Chatbot\Includes\Database_Strategy_Interface;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Services\Helper;

/**
 * File Data Object
 *
 * Represents a file stored in the WordPress options table for use with AI providers.
 * All files are stored in a single setting as an array and can be used for knowledge
 * generation, fine-tuning, or other AI-related purposes.
 *
 * File Lifecycle:
 * 1. Created via File_Upload_Service during batch upload
 * 2. Uploaded to AI provider via File_Service
 * 3. Used for knowledge generation via File_Knowledge_Generator
 * 4. Can be refreshed or deleted as needed
 *
 * Storage: All files are stored in WordPress options table under 'lbaic.chatbot.files'
 * Physical files are stored in WordPress uploads directory with unique names.
 *
 * @package Limb_Chatbot\Includes\Data_Objects
 * @since 1.0.0
 */
class File extends Data_Object {

	/**
	 * Purpose constant for knowledge generation files.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	const PURPOSE_KNOWLEDGE = 'knowledge';

	/**
	 * WordPress option name where all files are stored.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	const SETTING_NAME = 'lbaic.chatbot.files';

	/**
	 * File status constant for active files.
	 *
	 * Active files are successfully uploaded to AI provider and ready for use.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	const STATUS_ACTIVE = 1;

	/**
	 * External status constant for active files on AI provider.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	const EXTERNAL_STATUS_ACTIVE = 1;

	/**
	 * File status constant for newly uploaded files.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	const EXTERNAL_STATUS_FAILED = 0;

	/**
	 * File status constant for files currently being processed.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	const EXTERNAL_STATUS_PROCESSING = 2;

	/**
	 * Unique identifier for the file.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public $uuid;

	/**
	 * Relative path to the file within WordPress uploads directory.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public $file_path;

	/**
	 * Unique filename with hash prefix for storage.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public $file_name;

	/**
	 * Original filename as uploaded by user.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public $original_name;

	/**
	 * File size in bytes.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	public $file_size;

	/**
	 * MIME type of the file.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public $mime_type;

	/**
	 * Creation timestamp in MySQL format.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public $created_at;

	/**
	 * Current status of the file (uploaded, active, etc.).
	 *
	 * @var string|int
	 * @since 1.0.0
	 */
	public $status;

	/**
	 * Additional metadata including AI provider information.
	 *
	 * Contains provider-specific data like external file IDs, upload status,
	 * and other configuration details.
	 *
	 * @var array|null
	 * @since 1.0.0
	 */
	public $metadata;

	/**
	 * Creates a new file record in the database.
	 *
	 * This method handles the complete file creation process:
	 * 1. Generates UUID if not provided
	 * 2. Sets creation timestamp
	 * 3. Retrieves existing files from database
	 * 4. Creates new File instance
	 * 5. Adds to collection and saves all files
	 *
	 * @param  array  $data  File data array with the following keys:
	 *                       - uuid (optional): Unique identifier
	 *                       - file_path: Relative path to file
	 *                       - file_name: Unique filename with hash
	 *                       - original_name: Original uploaded filename
	 *                       - file_size: File size in bytes
	 *                       - mime_type: MIME type of file
	 *                       - status: File status (default: 'uploaded')
	 *                       - created_at (optional): Creation timestamp
	 *                       - metadata (optional): Additional metadata
	 *
	 * @return static Created File instance.
	 * @throws Exception If UUID generation fails or database save fails.
	 * @since 1.0.0
	 */
	public static function create( $data ) {
		// Generate UUID if not provided
		if ( empty( $data['uuid'] ) ) {
			$data['uuid'] = Helper::get_uuid();
		}

		// Ensure created_at is set
		if ( empty( $data['created_at'] ) ) {
			$data['created_at'] = current_time( 'mysql' );
		}

		// Get all existing files
		$files = static::get_all();

		// Create new file instance
		$file = static::make( $data );

		// Add to the collection
		$files[ $data['uuid'] ] = $file->to_array();

		// Save all files
		static::save_all( $files );

		return $file;
	}

	/**
	 * Gets the unique identifier for this file.
	 *
	 * @return string The file UUID.
	 * @since 1.0.0
	 */
	public function get_uuid() {
		return $this->uuid;
	}

	/**
	 * Sets the unique identifier for this file.
	 *
	 * @param string $uuid The file UUID to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_uuid( $uuid ): void {
		$this->uuid = $uuid;
	}

	/**
	 * Gets all files from the database.
	 *
	 * Retrieves all file records stored in the WordPress options table.
	 * Returns an empty array if no files exist or if the stored data is invalid.
	 *
	 * @return array Array of file data indexed by UUID.
	 * @since 1.0.0
	 */
	public static function get_all(): array {
		$result = self::get_db_strategy()->find( static::SETTING_NAME );
		$files  = $result['value'] ?? [];

		return is_array( $files ) ? $files : [];
	}

	/**
	 * Gets the database strategy for file storage.
	 *
	 * Files are stored using the WordPress Options strategy, which stores
	 * all file data in a single WordPress option for efficient retrieval.
	 *
	 * @return Database_Strategy_Interface|null The database strategy instance.
	 * @since 1.0.0
	 */
	static function get_db_strategy(): ?Database_Strategy_Interface {
		return new WP_Options();
	}

	/**
	 * Converts the file object to an array representation.
	 *
	 * Includes all file properties including metadata for database storage.
	 * Metadata is included as-is (may be array, string, or null).
	 * This method is used when saving files to the database.
	 *
	 * @return array File data array with all properties:
	 *               - uuid: Unique identifier
	 *               - file_path: Relative file path
	 *               - file_name: Unique filename
	 *               - original_name: Original filename
	 *               - file_size: File size in bytes
	 *               - mime_type: MIME type
	 *               - created_at: Creation timestamp
	 *               - status: File status
	 *               - metadata: Additional metadata
	 * @since 1.0.0
	 */
	public function to_array(): array {
		return [
			'uuid'          => $this->uuid,
			'file_path'     => $this->file_path,
			'file_name'     => $this->file_name,
			'original_name' => $this->original_name,
			'file_size'     => $this->file_size,
			'mime_type'     => $this->mime_type,
			'created_at'    => $this->created_at,
			'status'        => $this->status,
			'metadata'      => $this->metadata,
		];
	}

	/**
	 * Saves all files to the database.
	 *
	 * Updates the WordPress option with the complete file collection.
	 * This method is used internally by create(), update(), and delete() methods.
	 *
	 * @param  array  $files  Array of file data indexed by UUID.
	 *
	 * @return bool True on success, false on failure.
	 * @since 1.0.0
	 */
	protected static function save_all( array $files ): bool {
		$result = self::get_db_strategy()->update(
			[ 'key' => static::SETTING_NAME ],
			[ 'value' => $files ]
		);

		return ! empty( $result );
	}

	/**
	 * Updates an existing file record in the database.
	 *
	 * This method handles partial updates by merging existing data with new data.
	 * The UUID cannot be changed during updates. Only the provided fields are updated.
	 *
	 * @param  string  $where  File UUID to update.
	 * @param  array  $data  Updated data array. Can include any file properties.
	 *
	 * @return static|null Updated File instance or null if not found.
	 * @throws Exception If file with the given UUID is not found.
	 * @since 1.0.0
	 */
	public static function update( $where, $data ): ?File {
		$files = static::get_all();
		if ( is_array( $where ) ) {
			return null;
		}
		$uuid = $where;

		if ( ! isset( $files[ $uuid ] ) ) {
			throw new Exception(
				Error_Codes::FILE_NOT_FOUND,
				sprintf( __( 'File with UUID %s not found.', 'limb-chatbot' ), $uuid )
			);
		}

		// Merge existing data with new data
		$updated_data         = array_merge( $files[ $uuid ], $data );
		$updated_data['uuid'] = $uuid; // Ensure UUID doesn't change

		// Update the file in collection
		$files[ $uuid ] = $updated_data;

		// Save all files
		static::save_all( $files );

		return static::make( $updated_data );
	}

	/**
	 * Finds a file by its unique identifier.
	 *
	 * Searches through all stored files to find one with the matching UUID.
	 * Returns a File instance if found, null otherwise.
	 *
	 * @param  string  $uuid  File UUID to search for.
	 *
	 * @return static|null File instance or null if not found.
	 * @since 1.0.0
	 */
	public static function find_by_uuid( string $uuid ): ?File {
		$files = static::get_all();

		if ( isset( $files[ $uuid ] ) ) {
			return static::make( $files[ $uuid ] );
		}

		return null;
	}

	/**
	 * Finds a file by its unique filename.
	 *
	 * Searches through all stored files to find one with the matching filename.
	 * This searches the 'file_name' field which contains the unique hash-prefixed name.
	 *
	 * @param  string  $file_name  Unique filename to search for (with hash prefix).
	 *
	 * @return static|null File instance or null if not found.
	 * @since 1.0.0
	 */
	public static function find_by_name( string $file_name ): ?File {
		$files = static::get_all();

		foreach ( $files as $file_data ) {
			if ( isset( $file_data['file_name'] ) && $file_data['file_name'] === $file_name ) {
				return static::make( $file_data );
			}
		}

		return null;
	}

	/**
	 * Deletes a file record and its physical file from disk.
	 *
	 * This method performs a complete cleanup:
	 * 1. Removes the file record from the database
	 * 2. Attempts to delete the physical file from disk
	 * 3. Continues with database cleanup even if physical file deletion fails
	 *
	 * @param  string  $where  File UUID to delete.
	 *
	 * @return bool True on success, false on failure.
	 * @since 1.0.0
	 */
	public static function delete( $where ): bool {
		$uuid  = $where;
		$files = static::get_all();

		if ( ! isset( $files[ $uuid ] ) ) {
			return false;
		}

		// Get file data before deletion
		$file_data = $files[ $uuid ];
		$file_path = Helper::get_wp_uploaded_file_dir( $file_data['file_path'] ) ?? null;

		// Delete the physical file if it exists
		if ( $file_path && file_exists( $file_path ) ) {
			unlink( $file_path );
		}

		// Remove from files array
		unset( $files[ $uuid ] );

		// Save updated files array
		return static::save_all( $files );
	}

	/**
	 * Gets all files as File objects.
	 *
	 * @return array Array of File instances.
	 * @since 1.0.0
	 */
	public static function get_all_as_objects(): array {
		$files   = static::get_all();
		$objects = [];

		foreach ( $files as $file_data ) {
			$objects[] = static::make( $file_data );
		}

		return $objects;
	}

	/**
	 * Gets the relative file path within WordPress uploads directory.
	 *
	 * @return string The relative file path.
	 * @since 1.0.0
	 */
	public function get_file_path() {
		return $this->file_path;
	}

	/**
	 * Gets the full URL to the file.
	 *
	 * Constructs the complete URL to access the file via HTTP.
	 * Uses WordPress uploads URL base and the relative file path.
	 *
	 * @return string The full URL to the file.
	 * @since 1.0.0
	 */
	public function get_url() {
		$upload_dir = wp_upload_dir();

		return $upload_dir['baseurl'] . '/' . ltrim( $this->file_path, '/' );
	}

	/**
	 * Sets the relative file path within WordPress uploads directory.
	 *
	 * @param string $file_path The relative file path to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_file_path( $file_path ): void {
		$this->file_path = $file_path;
	}

	/**
	 * Gets the unique filename with hash prefix.
	 *
	 * @return string The unique filename.
	 * @since 1.0.0
	 */
	public function get_file_name() {
		return $this->file_name;
	}

	/**
	 * Sets the unique filename with hash prefix.
	 *
	 * @param string $file_name The unique filename to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_file_name( $file_name ): void {
		$this->file_name = $file_name;
	}

	/**
	 * Gets the original filename as uploaded by user.
	 *
	 * @return string The original filename.
	 * @since 1.0.0
	 */
	public function get_original_name() {
		return $this->original_name;
	}

	/**
	 * Sets the original filename as uploaded by user.
	 *
	 * @param string $original_name The original filename to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_original_name( $original_name ): void {
		$this->original_name = $original_name;
	}

	/**
	 * Gets the file size in bytes.
	 *
	 * @return int The file size in bytes.
	 * @since 1.0.0
	 */
	public function get_file_size() {
		return $this->file_size;
	}

	/**
	 * Sets the file size in bytes.
	 *
	 * @param int $file_size The file size in bytes to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_file_size( $file_size ): void {
		$this->file_size = $file_size;
	}

	/**
	 * Gets the MIME type of the file.
	 *
	 * @return string The MIME type.
	 * @since 1.0.0
	 */
	public function get_mime_type() {
		return $this->mime_type;
	}

	/**
	 * Sets the MIME type of the file.
	 *
	 * @param string $mime_type The MIME type to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_mime_type( $mime_type ): void {
		$this->mime_type = $mime_type;
	}

	/**
	 * Gets the creation timestamp in MySQL format.
	 *
	 * @return string The creation timestamp.
	 * @since 1.0.0
	 */
	public function get_created_at() {
		return $this->created_at;
	}

	/**
	 * Sets the creation timestamp in MySQL format.
	 *
	 * @param string $created_at The creation timestamp to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_created_at( $created_at ): void {
		$this->created_at = $created_at;
	}

	/**
	 * Gets the current status of the file.
	 *
	 * @return string|int The file status.
	 * @since 1.0.0
	 */
	public function get_status() {
		return $this->status;
	}

	/**
	 * Sets the current status of the file.
	 *
	 * @param string|int $status The file status to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_status( $status ): void {
		$this->status = $status;
	}

	/**
	 * Adds a metadata key-value pair to the file.
	 *
	 * This method is used to store AI provider-specific information like
	 * external file IDs, upload status, and configuration details.
	 *
	 * @param string $key The metadata key.
	 * @param mixed $value The metadata value.
	 * @return void
	 * @since 1.0.0
	 */
	public function add_meta( string $key, mixed $value ) {
		$metadata         = $this->get_metadata() ?? [];
		$metadata[ $key ] = $value;;
		$this->set_metadata( $metadata );
	}

	/**
	 * Gets all metadata associated with this file.
	 *
	 * @return array|null The metadata array or null if not set.
	 * @since 1.0.0
	 */
	public function get_metadata() {
		return $this->metadata;
	}

	/**
	 * Sets the complete metadata array for this file.
	 *
	 * @param array|null $metadata The metadata array to set.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_metadata( $metadata ): void {
		$this->metadata = $metadata;
	}

	/**
	 * Updates a metadata key-value pair and saves the file.
	 *
	 * This method updates metadata and immediately saves the file to the database.
	 * Use this when you want to persist changes immediately.
	 *
	 * @param string $key The metadata key to update.
	 * @param mixed $value The new metadata value.
	 * @return void
	 * @throws Exception If save fails.
	 * @since 1.0.0
	 */
	public function update_meta( string $key, $value ) {
		$metadata         = $this->get_metadata() ?? [];
		$metadata[ $key ] = $value;
		$this->set_metadata( $metadata );
		$this->save();
	}

	/**
	 * Saves the current file instance to the database.
	 *
	 * Updates the file record in the WordPress options table with the current
	 * state of this File object. Requires a valid UUID to be set.
	 *
	 * @return void
	 * @throws Exception If UUID is missing or database save fails.
	 * @since 1.0.0
	 */
	public function save(): void {
		if ( empty( $this->uuid ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_REQUIRED,
				__( 'UUID is required to save a file.', 'limb-chatbot' )
			);
		}

		$files                = static::get_all();
		$files[ $this->uuid ] = $this->to_array();
		static::save_all( $files );
	}

	/**
	 * Gets error information associated with this file.
	 *
	 * @return mixed Error information or null if not set.
	 * @since 1.0.0
	 */
	public function get_errors() {
		return $this->get_meta( 'errors' );
	}

	/**
	 * Gets a specific metadata value by key.
	 *
	 * @param string $key The metadata key to retrieve.
	 * @return mixed The metadata value or null if not found.
	 * @since 1.0.0
	 */
	public function get_meta( string $key ) {
		$metadata = $this->get_metadata() ?? [];

		return $metadata[ $key ] ?? null;
	}
}