//! Scene management API handlers for admin UI.

use axum::{
    Json,
    extract::{Path, State},
};
use chattyness_db::{
    models::{CreateSceneRequest, Scene, SceneSummary, UpdateSceneRequest},
    queries::{realms, scenes},
};
use chattyness_error::AppError;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::path::PathBuf;
use uuid::Uuid;

// =============================================================================
// Image Processing Helpers
// =============================================================================

/// Result of downloading and storing a background image.
struct ImageDownloadResult {
    /// The local path to the stored image (relative to static root, for URL).
    local_path: String,
    /// Image dimensions if requested.
    dimensions: Option<(u32, u32)>,
}

/// Download an image from a URL and store it locally.
///
/// Returns the local path and optionally the dimensions.
/// Path format: /static/realm/{realm_id}/scene/{scene_id}/{sha256}.{ext}
async fn download_and_store_image(
    url: &str,
    realm_id: Uuid,
    scene_id: Uuid,
    extract_dimensions: bool,
) -> Result<ImageDownloadResult, AppError> {
    use sha2::{Digest, Sha256};

    // Validate URL
    if !url.starts_with("http://") && !url.starts_with("https://") {
        return Err(AppError::Validation(
            "Image URL must start with http:// or https://".to_string(),
        ));
    }

    // Download the image
    let client = reqwest::Client::new();
    let response = client
        .get(url)
        .header(
            reqwest::header::USER_AGENT,
            "Chattyness/1.0 (Background image downloader)",
        )
        .header(reqwest::header::ACCEPT, "image/*")
        .send()
        .await
        .map_err(|e| AppError::Internal(format!("Failed to fetch image: {}", e)))?;

    // Check content type
    let content_type = response
        .headers()
        .get(reqwest::header::CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    // Determine extension from content type
    let ext = match content_type {
        t if t.starts_with("image/jpeg") => "jpg",
        t if t.starts_with("image/png") => "png",
        t if t.starts_with("image/gif") => "gif",
        t if t.starts_with("image/webp") => "webp",
        _ => {
            // Try to infer from URL
            if url.contains(".jpg") || url.contains(".jpeg") {
                "jpg"
            } else if url.contains(".png") {
                "png"
            } else if url.contains(".gif") {
                "gif"
            } else if url.contains(".webp") {
                "webp"
            } else {
                return Err(AppError::Validation(format!(
                    "Unsupported image type: {}",
                    content_type
                )));
            }
        }
    };

    // Get the image bytes
    let bytes = response
        .bytes()
        .await
        .map_err(|e| AppError::Internal(format!("Failed to read image data: {}", e)))?;

    // Compute SHA256 hash of the image content
    let mut hasher = Sha256::new();
    hasher.update(&bytes);
    let hash = hex::encode(hasher.finalize());

    // Extract dimensions if requested
    let dimensions = if extract_dimensions {
        let reader = image::ImageReader::new(std::io::Cursor::new(&bytes))
            .with_guessed_format()
            .map_err(|e| AppError::Internal(format!("Failed to detect image format: {}", e)))?;

        let dims = reader
            .into_dimensions()
            .map_err(|e| AppError::Internal(format!("Failed to read image dimensions: {}", e)))?;

        Some(dims)
    } else {
        None
    };

    // Create directory structure: /srv/chattyness/assets/realm/{realm_id}/scene/{scene_id}/
    let dir_path = PathBuf::from("/srv/chattyness/assets")
        .join("realm")
        .join(realm_id.to_string())
        .join("scene")
        .join(scene_id.to_string());

    tokio::fs::create_dir_all(&dir_path)
        .await
        .map_err(|e| AppError::Internal(format!("Failed to create directory: {}", e)))?;

    // Write the file with SHA256 hash as filename
    let filename = format!("{}.{}", hash, ext);
    let file_path = dir_path.join(&filename);

    tokio::fs::write(&file_path, &bytes)
        .await
        .map_err(|e| AppError::Internal(format!("Failed to write image file: {}", e)))?;

    // Return the URL path (relative to server root)
    let local_path = format!("/static/realm/{}/scene/{}/{}", realm_id, scene_id, filename);

    Ok(ImageDownloadResult {
        local_path,
        dimensions,
    })
}

/// Delete all image files for a scene.
async fn delete_scene_images(realm_id: Uuid, scene_id: Uuid) -> Result<(), AppError> {
    let dir_path = PathBuf::from("/srv/chattyness/assets")
        .join("realm")
        .join(realm_id.to_string())
        .join("scene")
        .join(scene_id.to_string());

    // Try to remove all files in the directory
    if let Ok(mut entries) = tokio::fs::read_dir(&dir_path).await {
        while let Ok(Some(entry)) = entries.next_entry().await {
            let path = entry.path();
            if path.is_file() {
                tokio::fs::remove_file(&path).await.ok();
            }
        }
    }

    Ok(())
}

// =============================================================================
// API Types
// =============================================================================

/// Query parameters for scene list.
#[derive(Debug, Deserialize)]
pub struct ListScenesQuery {
    pub limit: Option<i64>,
    pub offset: Option<i64>,
}

/// List all scenes for a realm.
pub async fn list_scenes(
    State(pool): State<PgPool>,
    Path(slug): Path<String>,
) -> Result<Json<Vec<SceneSummary>>, AppError> {
    // Get the realm
    let realm = realms::get_realm_by_slug(&pool, &slug)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;

    let scene_list = scenes::list_scenes_for_realm(&pool, realm.id).await?;
    Ok(Json(scene_list))
}

/// Get a scene by ID.
pub async fn get_scene(
    State(pool): State<PgPool>,
    Path(scene_id): Path<Uuid>,
) -> Result<Json<Scene>, AppError> {
    let scene = scenes::get_scene_by_id(&pool, scene_id)
        .await?
        .ok_or_else(|| AppError::NotFound("Scene not found".to_string()))?;
    Ok(Json(scene))
}

/// Create scene response.
#[derive(Debug, Serialize)]
pub struct CreateSceneResponse {
    pub id: Uuid,
    pub slug: String,
}

/// Create a new scene in a realm.
pub async fn create_scene(
    State(pool): State<PgPool>,
    Path(slug): Path<String>,
    Json(mut req): Json<CreateSceneRequest>,
) -> Result<Json<CreateSceneResponse>, AppError> {
    // Get the realm
    let realm = realms::get_realm_by_slug(&pool, &slug)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;

    // Check if slug is available
    let available = scenes::is_scene_slug_available(&pool, realm.id, &req.slug).await?;
    if !available {
        return Err(AppError::Conflict(format!(
            "Scene slug '{}' is already taken in this realm",
            req.slug
        )));
    }

    // Generate a temporary scene ID for image storage path
    let scene_id = Uuid::new_v4();

    // Handle background image URL - download and store locally
    if let Some(ref url) = req.background_image_url {
        if !url.is_empty() {
            let result =
                download_and_store_image(url, realm.id, scene_id, req.infer_dimensions_from_image)
                    .await?;

            req.background_image_path = Some(result.local_path);

            if let Some((width, height)) = result.dimensions {
                req.bounds_wkt = Some(format!(
                    "POLYGON((0 0, {} 0, {} {}, 0 {}, 0 0))",
                    width, width, height, height
                ));
            }
        }
    }

    let scene = scenes::create_scene_with_id(&pool, scene_id, realm.id, &req).await?;
    Ok(Json(CreateSceneResponse {
        id: scene.id,
        slug: scene.slug,
    }))
}

/// Update a scene.
pub async fn update_scene(
    State(pool): State<PgPool>,
    Path(scene_id): Path<Uuid>,
    Json(mut req): Json<UpdateSceneRequest>,
) -> Result<Json<Scene>, AppError> {
    // Get the existing scene to get realm_id
    let existing_scene = scenes::get_scene_by_id(&pool, scene_id)
        .await?
        .ok_or_else(|| AppError::NotFound("Scene not found".to_string()))?;

    // Handle clear background image
    if req.clear_background_image {
        delete_scene_images(existing_scene.realm_id, scene_id).await?;
        req.background_image_path = Some(String::new());
    }
    // Handle new background image URL - download and store locally
    else if let Some(ref url) = req.background_image_url {
        if !url.is_empty() {
            delete_scene_images(existing_scene.realm_id, scene_id).await?;

            let result = download_and_store_image(
                url,
                existing_scene.realm_id,
                scene_id,
                req.infer_dimensions_from_image,
            )
            .await?;

            req.background_image_path = Some(result.local_path);

            if let Some((width, height)) = result.dimensions {
                req.bounds_wkt = Some(format!(
                    "POLYGON((0 0, {} 0, {} {}, 0 {}, 0 0))",
                    width, width, height, height
                ));
            }
        }
    }

    let scene = scenes::update_scene(&pool, scene_id, &req).await?;
    Ok(Json(scene))
}

/// Delete a scene.
pub async fn delete_scene(
    State(pool): State<PgPool>,
    Path(scene_id): Path<Uuid>,
) -> Result<Json<()>, AppError> {
    scenes::delete_scene(&pool, scene_id).await?;
    Ok(Json(()))
}
