use crate::AppState;
use crate::error::{AppError, Result};
use crate::transcoding::{JobPriority, QualityProfile};
use axum::{
    extract::{Multipart, Path, State},
    http::StatusCode,
    response::Response,
    Json,
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;

/// Request pour soumettre un job de transcodage
#[derive(Debug, Deserialize)]
pub struct TranscodeRequest {
    pub codec: Option<String>,
    pub bitrate: Option<u32>,
    pub quality_profile: Option<String>,
    pub priority: Option<String>,
}

/// Response après soumission d'un job
#[derive(Debug, Serialize)]
pub struct TranscodeResponse {
    pub job_id: Uuid,
    pub status: String,
    pub message: String,
}

/// Response pour l'état d'un job
#[derive(Debug, Serialize)]
pub struct JobStatusResponse {
    pub id: Uuid,
    pub track_id: String,
    pub status: String,
    pub progress: f32,
    pub created_at: String,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
    pub error: Option<String>,
}

/// POST /v1/stream/transcode
/// Soumet un fichier audio pour transcodage
pub async fn transcode_handler(
    State(state): State<AppState>,
    mut multipart: Multipart,
) -> Result<Json<TranscodeResponse>> {
    let mut file_path: Option<PathBuf> = None;
    let mut codec: Option<String> = None;
    let mut bitrate: Option<u32> = None;
    let mut quality_profile: Option<String> = None;
    let mut priority: Option<String> = None;
    let mut track_id: Option<String> = None;

    // Parser le multipart form
    while let Some(field) = multipart.next_field().await.map_err(|e| AppError::InvalidData {
        message: format!("Failed to parse multipart: {}", e),
    })? {
        let field_name = field.name().unwrap_or("");
        match field_name {
            "file" => {
                let filename = field.file_name()
                    .ok_or_else(|| AppError::InvalidData {
                        message: "Missing filename in file field".to_string(),
                    })?
                    .to_string();
                
                // Générer un track_id basé sur le nom de fichier ou UUID
                track_id = Some(
                    filename
                        .trim_end_matches(|c: char| c == '.' || c.is_alphanumeric())
                        .to_string()
                        .replace(" ", "_")
                        .replace(".", "_")
                );

                // Sauvegarder le fichier temporairement
                let temp_dir = std::env::temp_dir();
                let temp_file = temp_dir.join(format!("{}_{}", 
                    Uuid::new_v4(), 
                    filename
                ));
                
                let data = field.bytes().await.map_err(|e| AppError::InvalidData {
                    message: format!("Failed to read file data: {}", e),
                })?;
                
                tokio::fs::write(&temp_file, data).await.map_err(|e| AppError::InternalError {
                    message: format!("Failed to save uploaded file: {}", e),
                })?;
                
                file_path = Some(temp_file);
            }
            "codec" => {
                codec = Some(
                    field.text().await.map_err(|e| AppError::InvalidData {
                        message: format!("Failed to read codec: {}", e),
                    })?
                );
            }
            "bitrate" => {
                bitrate = field.text().await
                    .ok()
                    .and_then(|s| s.parse::<u32>().ok());
            }
            "quality_profile" => {
                quality_profile = Some(
                    field.text().await.map_err(|e| AppError::InvalidData {
                        message: format!("Failed to read quality_profile: {}", e),
                    })?
                );
            }
            "priority" => {
                priority = Some(
                    field.text().await.map_err(|e| AppError::InvalidData {
                        message: format!("Failed to read priority: {}", e),
                    })?
                );
            }
            _ => {}
        }
    }

    // Validation
    let input_path = file_path.ok_or_else(|| AppError::InvalidData {
        message: "Missing file field".to_string(),
    })?;

    let track_id = track_id.unwrap_or_else(|| Uuid::new_v4().to_string());

    // Déterminer le profil de qualité
    let profile = match quality_profile.as_deref() {
        Some("hi_res") => QualityProfile::hi_res(),
        Some("high") => QualityProfile::high(),
        Some("medium") => QualityProfile::medium(),
        Some("low") => QualityProfile::low(),
        _ => QualityProfile::medium(), // Default
    };

    // Override codec et bitrate si fournis
    let profile = if let Some(codec_str) = codec.as_deref() {
        let mut p = profile;
        p.codec = match codec_str {
            "aac" => crate::transcoding::codecs::AudioCodec::AAC,
            "mp3" => crate::transcoding::codecs::AudioCodec::MP3,
            "opus" => crate::transcoding::codecs::AudioCodec::OPUS,
            "flac" => crate::transcoding::codecs::AudioCodec::FLAC,
            _ => p.codec,
        };
        p
    } else {
        profile
    };

    let profile = if let Some(bitrate_val) = bitrate {
        let mut p = profile;
        p.bitrate = bitrate_val;
        p
    } else {
        profile
    };

    // Déterminer la priorité
    let job_priority = match priority.as_deref() {
        Some("urgent") => JobPriority::Urgent,
        Some("background") => JobPriority::Background,
        _ => JobPriority::Normal,
    };

    // Créer le répertoire de sortie
    let output_dir = PathBuf::from(&state.config.compression.output_dir)
        .join(format!("transcode_{}", Uuid::new_v4()));

    // Soumettre le job
    let job_id = state.transcoding_engine
        .submit(
            track_id,
            input_path,
            output_dir,
            profile,
            job_priority,
        )
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to submit transcoding job: {}", e),
        })?;

    Ok(Json(TranscodeResponse {
        job_id,
        status: "queued".to_string(),
        message: "Job submitted successfully".to_string(),
    }))
}

/// GET /v1/stream/job/{id}
/// Récupère l'état d'un job de transcodage
pub async fn get_job_status(
    Path(job_id): Path<Uuid>,
    State(state): State<AppState>,
) -> Result<Json<JobStatusResponse>> {
    let job_manager = state.transcoding_engine.job_manager();
    let job = job_manager.get_status(job_id).await
        .ok_or_else(|| AppError::NotFound {
            resource: format!("Job {}", job_id),
        })?;

    Ok(Json(JobStatusResponse {
        id: job.id,
        track_id: job.track_id,
        status: format!("{:?}", job.status),
        progress: job.progress_percent,
        created_at: format!("{:?}", job.created_at),
        started_at: job.started_at.map(|t| format!("{:?}", t)),
        completed_at: job.completed_at.map(|t| format!("{:?}", t)),
        error: match &job.status {
            crate::transcoding::JobStatus::Failed(err) => Some(err.clone()),
            _ => None,
        },
    }))
}

/// GET /v1/stream/hls/{job_id}/index.m3u8
/// Sert le manifest HLS pour un job
pub async fn serve_hls_manifest(
    Path(job_id): Path<Uuid>,
    State(state): State<AppState>,
) -> Result<Response> {
    let job_manager = state.transcoding_engine.job_manager();
    let job = job_manager.get_status(job_id).await
        .ok_or_else(|| AppError::NotFound {
            resource: format!("Job {}", job_id),
        })?;

    // Vérifier que le job est terminé
    if !matches!(job.status, crate::transcoding::JobStatus::Completed) {
        return Err(AppError::InvalidData {
            message: format!("Job {} is not completed yet (status: {:?})", job_id, job.status),
        });
    }

    let manifest_path = job.output_dir.join("index.m3u8");
    
    if !manifest_path.exists() {
        return Err(AppError::NotFound {
            resource: format!("HLS manifest for job {}", job_id),
        });
    }

    let content = tokio::fs::read_to_string(&manifest_path).await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to read HLS manifest: {}", e),
        })?;

    Ok(Response::builder()
        .status(StatusCode::OK)
        .header("Content-Type", "application/vnd.apple.mpegurl")
        .body(content.into())
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to create response: {}", e),
        })?)
}

/// GET /v1/stream/hls/{job_id}/{segment}
/// Sert un segment HLS (.ts)
pub async fn serve_hls_segment(
    Path((job_id, segment)): Path<(Uuid, String)>,
    State(state): State<AppState>,
) -> Result<Response> {
    let job_manager = state.transcoding_engine.job_manager();
    let job = job_manager.get_status(job_id).await
        .ok_or_else(|| AppError::NotFound {
            resource: format!("Job {}", job_id),
        })?;

    let segment_path = job.output_dir.join(&segment);
    
    if !segment_path.exists() {
        return Err(AppError::NotFound {
            resource: format!("Segment {} for job {}", segment, job_id),
        });
    }

    let content = tokio::fs::read(&segment_path).await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to read segment: {}", e),
        })?;

    Ok(Response::builder()
        .status(StatusCode::OK)
        .header("Content-Type", "video/mp2t")
        .body(content.into())
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to create response: {}", e),
        })?)
}

/// Response pour le statut détaillé d'un job avec segments
#[derive(Debug, Serialize)]
pub struct JobStatusDetailedResponse {
    pub id: Uuid,
    pub track_id: String,
    pub status: String,
    pub segments: Vec<SegmentInfo>,
    pub current_duration: f64,
    pub progress: f32,
    pub created_at: String,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
    pub error: Option<String>,
}

/// Information sur un segment
#[derive(Debug, Serialize)]
pub struct SegmentInfo {
    pub index: i32,
    pub path: String,
    pub duration: f64,
    pub created_at: String,
}

/// GET /api/streams/jobs/{id}/status
/// Récupère le statut détaillé d'un job avec segments en temps réel
pub async fn get_job_status_detailed(
    Path(job_id): Path<Uuid>,
    State(state): State<AppState>,
) -> Result<Json<JobStatusDetailedResponse>> {
    use crate::database::pool::create_pool_from_config;

    // Créer un pool temporaire depuis la config
    let pool = create_pool_from_config(&state.config.database).await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to create database pool: {}", e),
        })?;

    // Récupérer le job depuis la DB
    let job_row = sqlx::query(
        r#"
        SELECT id, track_id, status, created_at, updated_at, error_message
        FROM stream_jobs
        WHERE id = $1
        "#,
    )
    .bind(job_id)
    .fetch_optional(&pool)
    .await
    .map_err(|e| AppError::InternalError {
        message: format!("Failed to query job: {}", e),
    })?;

    let job = job_row.ok_or_else(|| AppError::NotFound {
        resource: format!("Job {}", job_id),
    })?;

    use sqlx::Row;
    let track_id_uuid: Uuid = job.get("track_id");

    // Récupérer les segments depuis la DB
    let segments_rows = sqlx::query(
        r#"
        SELECT segment_index, path, duration, created_at
        FROM stream_segments
        WHERE track_id = $1
        ORDER BY segment_index ASC
        "#,
    )
    .bind(track_id_uuid)
    .fetch_all(&pool)
    .await
    .map_err(|e| AppError::InternalError {
        message: format!("Failed to query segments: {}", e),
    })?;

    let segments: Vec<SegmentInfo> = segments_rows
        .into_iter()
        .map(|row| SegmentInfo {
            index: row.get("segment_index"),
            path: row.get("path"),
            duration: row.get("duration"),
            created_at: row.get::<chrono::DateTime<chrono::Utc>, _>("created_at").to_rfc3339(),
        })
        .collect();

    // Calculer la durée totale
    let current_duration: f64 = segments.iter().map(|s| s.duration).sum();

    // Calculer la progression (basé sur le statut)
    let status_str: String = job.get("status");
    let progress = match status_str.as_str() {
        "done" => 1.0,
        "error" => 0.0,
        "encoding" => {
            // Estimation basée sur le nombre de segments (approximatif)
            if current_duration > 0.0 {
                // On suppose que la durée totale est connue, sinon on utilise 0.5
                0.5
            } else {
                0.1
            }
        }
        _ => 0.0,
    };

    Ok(Json(JobStatusDetailedResponse {
        id: job.get("id"),
        track_id: job.get::<Uuid, _>("track_id").to_string(),
        status: job.get("status"),
        segments,
        current_duration,
        progress,
        created_at: job.get::<chrono::DateTime<chrono::Utc>, _>("created_at").to_rfc3339(),
        started_at: None, // TODO: Ajouter started_at dans stream_jobs si nécessaire
        completed_at: if job.get::<String, _>("status") == "done" {
            Some(job.get::<chrono::DateTime<chrono::Utc>, _>("updated_at").to_rfc3339())
        } else {
            None
        },
        error: job.get("error_message"),
    }))
}

