//! Thumbnail generation action handlers

use super::{
	job::{ThumbnailJob, ThumbnailJobConfig},
	processor::ThumbnailProcessor,
};
use crate::{
	context::CoreContext,
	infra::action::{error::ActionError, LibraryAction},
	ops::indexing::{path_resolver::PathResolver, processor::ProcessorEntry},
};
use specta::Type;
use std::sync::Arc;
use uuid::Uuid;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Type)]
pub struct ThumbnailInput {
	pub paths: Vec<std::path::PathBuf>,
	pub size: u32,
	pub quality: u8,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ThumbnailAction {
	input: ThumbnailInput,
}

impl ThumbnailAction {
	/// Create a new thumbnail generation action
	pub fn new(input: ThumbnailInput) -> Self {
		Self { input }
	}
}

// Implement the unified LibraryAction (replaces ActionHandler)
impl LibraryAction for ThumbnailAction {
	type Input = ThumbnailInput;
	type Output = crate::infra::job::handle::JobReceipt;

	fn from_input(input: ThumbnailInput) -> Result<Self, String> {
		Ok(ThumbnailAction::new(input))
	}

	async fn execute(
		self,
		library: std::sync::Arc<crate::library::Library>,
		_context: Arc<CoreContext>,
	) -> Result<Self::Output, ActionError> {
		// Create thumbnail job config from size
		let config = ThumbnailJobConfig::from_sizes(vec![self.input.size]);

		// Create job instance directly
		let job = ThumbnailJob::new(config);

		// Dispatch job and return handle directly
		let job_handle = library
			.jobs()
			.dispatch(job)
			.await
			.map_err(ActionError::Job)?;

		Ok(job_handle.into())
	}

	fn action_kind(&self) -> &'static str {
		"media.thumbnail"
	}
}

// Register action
crate::register_library_action!(ThumbnailAction, "media.thumbnail");

// ============================================================================
// Regenerate Thumbnail Action (for single file UI triggering)
// ============================================================================

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Type)]
pub struct RegenerateThumbnailInput {
	/// UUID of the entry to regenerate thumbnails for
	pub entry_uuid: Uuid,
	/// Optional variant names (defaults to grid@1x, grid@2x, detail@1x)
	pub variants: Option<Vec<String>>,
	/// Force regeneration even if thumbnails exist
	pub force: bool,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Type)]
pub struct RegenerateThumbnailOutput {
	/// Number of thumbnails generated
	pub generated_count: usize,
	/// Variant names that were generated
	pub variants: Vec<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RegenerateThumbnailAction {
	input: RegenerateThumbnailInput,
}

impl RegenerateThumbnailAction {
	pub fn new(input: RegenerateThumbnailInput) -> Self {
		Self { input }
	}
}

impl LibraryAction for RegenerateThumbnailAction {
	type Input = RegenerateThumbnailInput;
	type Output = RegenerateThumbnailOutput;

	fn from_input(input: RegenerateThumbnailInput) -> Result<Self, String> {
		Ok(Self::new(input))
	}

	async fn execute(
		self,
		library: Arc<crate::library::Library>,
		_context: Arc<CoreContext>,
	) -> Result<Self::Output, ActionError> {
		use crate::infra::db::entities;
		use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};

		let db = library.db().conn();

		// Load entry by UUID
		let entry = entities::entry::Entity::find()
			.filter(entities::entry::Column::Uuid.eq(self.input.entry_uuid))
			.one(db)
			.await
			.map_err(|e| ActionError::Internal(format!("Failed to load entry: {}", e)))?
			.ok_or_else(|| ActionError::Internal("Entry not found".to_string()))?;

		// Get full path
		let path = PathResolver::get_full_path(db, entry.id)
			.await
			.map_err(|e| ActionError::Internal(format!("Failed to resolve path: {}", e)))?;

		// Get MIME type
		let mime_type = if let Some(content_id) = entry.content_id {
			if let Ok(Some(ci)) = entities::content_identity::Entity::find_by_id(content_id)
				.one(db)
				.await
			{
				if let Some(mime_id) = ci.mime_type_id {
					if let Ok(Some(mime)) = entities::mime_type::Entity::find_by_id(mime_id)
						.one(db)
						.await
					{
						Some(mime.mime_type)
					} else {
						None
					}
				} else {
					None
				}
			} else {
				None
			}
		} else {
			return Err(ActionError::Internal(
				"Entry has no content identity".to_string(),
			));
		};

		// Build processor entry
		let kind = match entry.kind {
			0 => crate::ops::indexing::state::EntryKind::File,
			1 => crate::ops::indexing::state::EntryKind::Directory,
			2 => crate::ops::indexing::state::EntryKind::Symlink,
			_ => crate::ops::indexing::state::EntryKind::File,
		};

		let proc_entry = ProcessorEntry {
			id: entry.id,
			uuid: entry.uuid,
			path: path.clone(),
			kind,
			size: entry.size as u64,
			content_id: entry.content_id,
			mime_type: mime_type.clone(),
		};

		// Create thumbnail processor with custom settings
		let mut processor =
			ThumbnailProcessor::new(library.clone()).with_regenerate(self.input.force);

		// Apply custom variants if provided
		if let Some(variant_names) = &self.input.variants {
			let settings = serde_json::json!({
				"variants": variant_names,
			});
			processor = processor
				.with_settings(&settings)
				.map_err(|e| ActionError::Internal(format!("Invalid settings: {}", e)))?;
		}

		// Check if processor should run
		if !processor.should_process(&proc_entry) {
			return Err(ActionError::Internal(
				"File type does not support thumbnails".to_string(),
			));
		}

		// Process the file - will fail with proper error if video without ffmpeg
		let result = processor
			.process(db, &proc_entry)
			.await
			.map_err(|e| ActionError::Internal(format!("Thumbnail generation failed: {}", e)))?;

		if !result.success {
			return Err(ActionError::Internal(
				result.error.unwrap_or_else(|| "Unknown error".to_string()),
			));
		}

		// Get variant names
		let variant_names: Vec<String> = processor
			.variants
			.iter()
			.map(|v| v.variant.as_str().to_string())
			.collect();

		Ok(RegenerateThumbnailOutput {
			generated_count: result.artifacts_created,
			variants: variant_names,
		})
	}

	fn action_kind(&self) -> &'static str {
		"media.thumbnail.regenerate"
	}
}

crate::register_library_action!(RegenerateThumbnailAction, "media.thumbnail.regenerate");
