// Podcast-import — API-endepunkter for import-wizard (oppgave 30.7).
//
// Tre endepunkter:
//   POST /admin/podcast/import-preview  — dry-run: hent og vis hva som ville importeres
//   POST /admin/podcast/import          — start faktisk import via jobbkø
//   GET  /admin/podcast/import-status   — hent status for en import-jobb
//
// Preview kjører synops-import-podcast --dry-run direkte (synkront).
// Import legger en jobb i køen som kjører CLI-verktøyet asynkront.
//
// Ref: docs/features/podcast_hosting.md § "Prøveimport-flyten"

use axum::{extract::State, http::StatusCode, Json};
use axum::extract::Query;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::auth::AuthUser;
use crate::cli_dispatch;
use crate::jobs;
use crate::AppState;

// =============================================================================
// Preview (dry-run)
// =============================================================================

#[derive(Deserialize)]
pub struct ImportPreviewRequest {
    pub feed_url: String,
    pub collection_id: Uuid,
}

#[derive(Serialize)]
pub struct ImportPreviewResponse {
    pub status: String,
    pub feed_url: String,
    pub feed_title: Option<String>,
    pub episodes_found: usize,
    pub episodes_imported: usize,
    pub episodes_skipped: usize,
    pub dry_run: bool,
    pub episodes: Vec<serde_json::Value>,
    pub errors: Vec<String>,
}

/// POST /admin/podcast/import-preview
///
/// Kjører synops-import-podcast --dry-run og returnerer resultatet.
/// Synkront — feeden hentes og parses, men ingenting skrives.
pub async fn import_preview(
    user: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<ImportPreviewRequest>,
) -> Result<Json<serde_json::Value>, StatusCode> {
    // Verifiser at samlingen eksisterer og har podcast-trait
    let collection_exists: bool = sqlx::query_scalar(
        "SELECT EXISTS(SELECT 1 FROM nodes WHERE id = $1 AND node_kind = 'collection')",
    )
    .bind(body.collection_id)
    .fetch_one(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    if !collection_exists {
        tracing::warn!(
            collection_id = %body.collection_id,
            "import-preview: samling finnes ikke"
        );
        return Err(StatusCode::NOT_FOUND);
    }

    // Kjør CLI-verktøyet med --dry-run
    let bin = import_podcast_bin();
    let mut cmd = tokio::process::Command::new(&bin);
    cmd.arg("--feed-url").arg(&body.feed_url)
        .arg("--collection-id").arg(body.collection_id.to_string())
        .arg("--created-by").arg(user.node_id.to_string())
        .arg("--dry-run");

    cli_dispatch::set_database_url(&mut cmd).map_err(|e| {
        tracing::error!("DATABASE_URL mangler: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;
    cli_dispatch::forward_env(&mut cmd, "CAS_ROOT");

    tracing::info!(
        feed_url = %body.feed_url,
        collection_id = %body.collection_id,
        user = %user.node_id,
        "Starter podcast import preview (dry-run)"
    );

    let result = cli_dispatch::run_cli_tool("synops-import-podcast", &mut cmd)
        .await
        .map_err(|e| {
            tracing::error!("import-preview feilet: {e}");
            StatusCode::INTERNAL_SERVER_ERROR
        })?;

    Ok(Json(result))
}

// =============================================================================
// Import (via jobbkø)
// =============================================================================

#[derive(Deserialize)]
pub struct ImportStartRequest {
    pub feed_url: String,
    pub collection_id: Uuid,
}

#[derive(Serialize)]
pub struct ImportStartResponse {
    pub job_id: Uuid,
}

/// POST /admin/podcast/import
///
/// Legger en import-jobb i køen. Returnerer job_id umiddelbart.
pub async fn import_start(
    user: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<ImportStartRequest>,
) -> Result<Json<ImportStartResponse>, StatusCode> {
    // Verifiser samling
    let collection_exists: bool = sqlx::query_scalar(
        "SELECT EXISTS(SELECT 1 FROM nodes WHERE id = $1 AND node_kind = 'collection')",
    )
    .bind(body.collection_id)
    .fetch_one(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    if !collection_exists {
        return Err(StatusCode::NOT_FOUND);
    }

    let payload = serde_json::json!({
        "feed_url": body.feed_url,
        "collection_id": body.collection_id.to_string(),
        "created_by": user.node_id.to_string(),
    });

    let job_id = jobs::enqueue(
        &state.db,
        "import_podcast",
        payload,
        Some(body.collection_id),
        3, // medium priority
    )
    .await
    .map_err(|e| {
        tracing::error!("Kunne ikke enqueue import_podcast: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    tracing::info!(
        job_id = %job_id,
        feed_url = %body.feed_url,
        collection_id = %body.collection_id,
        user = %user.node_id,
        "Podcast-import lagt i jobbkø"
    );

    Ok(Json(ImportStartResponse { job_id }))
}

// =============================================================================
// Import-status (poll)
// =============================================================================

#[derive(Deserialize)]
pub struct ImportStatusQuery {
    pub job_id: Uuid,
}

#[derive(Serialize)]
pub struct ImportStatusResponse {
    pub job_id: Uuid,
    pub status: String,
    pub result: Option<serde_json::Value>,
    pub error_msg: Option<String>,
    pub created_at: String,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
}

/// GET /admin/podcast/import-status?job_id=<uuid>
pub async fn import_status(
    _user: AuthUser,
    State(state): State<AppState>,
    Query(query): Query<ImportStatusQuery>,
) -> Result<Json<ImportStatusResponse>, StatusCode> {
    let row = sqlx::query_as::<_, (String, Option<serde_json::Value>, Option<String>, String, Option<String>, Option<String>)>(
        r#"
        SELECT status, result, error_msg,
               created_at::text, started_at::text, completed_at::text
        FROM job_queue
        WHERE id = $1
        "#,
    )
    .bind(query.job_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    match row {
        Some((status, result, error_msg, created_at, started_at, completed_at)) => {
            Ok(Json(ImportStatusResponse {
                job_id: query.job_id,
                status,
                result,
                error_msg,
                created_at,
                started_at,
                completed_at,
            }))
        }
        None => Err(StatusCode::NOT_FOUND),
    }
}

// =============================================================================
// Collections med podcast-trait (for dropdown)
// =============================================================================

#[derive(Serialize)]
pub struct PodcastCollection {
    pub id: Uuid,
    pub title: Option<String>,
    pub slug: Option<String>,
}

/// GET /admin/podcast/collections — samlinger med podcast-trait
pub async fn podcast_collections(
    _user: AuthUser,
    State(state): State<AppState>,
) -> Result<Json<Vec<PodcastCollection>>, StatusCode> {
    let rows = sqlx::query_as::<_, (Uuid, Option<String>, serde_json::Value)>(
        r#"
        SELECT id, title, COALESCE(metadata, '{}'::jsonb)
        FROM nodes
        WHERE node_kind = 'collection'
          AND (metadata->'traits'->'podcast' IS NOT NULL
               OR metadata->'traits'->'rss' IS NOT NULL)
        ORDER BY title
        "#,
    )
    .fetch_all(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let collections: Vec<PodcastCollection> = rows
        .into_iter()
        .map(|(id, title, metadata)| {
            let traits = metadata.get("traits");
            let slug = traits
                .and_then(|t| t.get("publishing"))
                .and_then(|p| p.get("slug"))
                .and_then(|s| s.as_str())
                .map(|s| s.to_string());
            PodcastCollection { id, title, slug }
        })
        .collect();

    Ok(Json(collections))
}

// =============================================================================
// Jobb-handler (kalles fra jobs::dispatch)
// =============================================================================

/// Handler for `import_podcast`-jobb i jobbkøen.
pub async fn handle_import_podcast(
    job: &jobs::JobRow,
) -> Result<serde_json::Value, String> {
    let feed_url = job.payload["feed_url"]
        .as_str()
        .ok_or("Mangler feed_url i payload")?;
    let collection_id = job.payload["collection_id"]
        .as_str()
        .ok_or("Mangler collection_id i payload")?;
    let created_by = job.payload["created_by"]
        .as_str()
        .ok_or("Mangler created_by i payload")?;

    let bin = import_podcast_bin();
    let mut cmd = tokio::process::Command::new(&bin);
    cmd.arg("--feed-url").arg(feed_url)
        .arg("--collection-id").arg(collection_id)
        .arg("--created-by").arg(created_by);

    cli_dispatch::set_database_url(&mut cmd)?;
    cli_dispatch::forward_env(&mut cmd, "CAS_ROOT");

    tracing::info!(
        job_id = %job.id,
        feed_url = feed_url,
        collection_id = collection_id,
        "Starter synops-import-podcast"
    );

    let result = cli_dispatch::run_cli_tool("synops-import-podcast", &mut cmd).await?;

    tracing::info!(
        job_id = %job.id,
        status = result["status"].as_str().unwrap_or("unknown"),
        imported = result["episodes_imported"].as_u64().unwrap_or(0),
        skipped = result["episodes_skipped"].as_u64().unwrap_or(0),
        "synops-import-podcast fullført"
    );

    Ok(result)
}

// =============================================================================
// Hjelpefunksjoner
// =============================================================================

/// Synops-import-podcast binary path.
fn import_podcast_bin() -> String {
    std::env::var("SYNOPS_IMPORT_PODCAST_BIN")
        .unwrap_or_else(|_| "synops-import-podcast".to_string())
}
