//! Release management API endpoints (admin only)

use axum::{
    extract::{Path, State},
    http::StatusCode,
    Json,
};
use serde::{Deserialize, Serialize};

use crate::auth::AdminUser;
use crate::db;
use crate::AppState;

use super::auth::ErrorResponse;

/// Release info response
#[derive(Debug, Serialize)]
pub struct ReleaseInfo {
    pub id: String,
    pub version: String,
    pub download_url: String,
    pub checksum_sha256: String,
    pub release_notes: Option<String>,
    pub is_stable: bool,
    pub is_mandatory: bool,
    pub min_version: Option<String>,
    pub created_at: String,
}

impl From<db::Release> for ReleaseInfo {
    fn from(r: db::Release) -> Self {
        Self {
            id: r.id.to_string(),
            version: r.version,
            download_url: r.download_url,
            checksum_sha256: r.checksum_sha256,
            release_notes: r.release_notes,
            is_stable: r.is_stable,
            is_mandatory: r.is_mandatory,
            min_version: r.min_version,
            created_at: r.created_at.to_rfc3339(),
        }
    }
}

/// Version info for unauthenticated endpoint
#[derive(Debug, Serialize)]
pub struct VersionInfo {
    pub latest_version: String,
    pub download_url: String,
    pub checksum_sha256: String,
    pub is_mandatory: bool,
    pub release_notes: Option<String>,
}

/// Create release request
#[derive(Debug, Deserialize)]
pub struct CreateReleaseRequest {
    pub version: String,
    pub download_url: String,
    pub checksum_sha256: String,
    pub release_notes: Option<String>,
    pub is_stable: bool,
    pub is_mandatory: bool,
    pub min_version: Option<String>,
}

/// Update release request
#[derive(Debug, Deserialize)]
pub struct UpdateReleaseRequest {
    pub release_notes: Option<String>,
    pub is_stable: bool,
    pub is_mandatory: bool,
}

/// GET /api/version - Get latest version info (no auth required)
pub async fn get_version(
    State(state): State<AppState>,
) -> Result<Json<VersionInfo>, (StatusCode, Json<ErrorResponse>)> {
    let db = state.db.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ErrorResponse {
                error: "Database not available".to_string(),
            }),
        )
    })?;

    let release = db::get_latest_stable_release(db.pool())
        .await
        .map_err(|e| {
            tracing::error!("Database error: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Failed to fetch version".to_string(),
                }),
            )
        })?
        .ok_or_else(|| {
            (
                StatusCode::NOT_FOUND,
                Json(ErrorResponse {
                    error: "No stable release available".to_string(),
                }),
            )
        })?;

    Ok(Json(VersionInfo {
        latest_version: release.version,
        download_url: release.download_url,
        checksum_sha256: release.checksum_sha256,
        is_mandatory: release.is_mandatory,
        release_notes: release.release_notes,
    }))
}

/// GET /api/releases - List all releases (admin only)
pub async fn list_releases(
    State(state): State<AppState>,
    _admin: AdminUser,
) -> Result<Json<Vec<ReleaseInfo>>, (StatusCode, Json<ErrorResponse>)> {
    let db = state.db.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ErrorResponse {
                error: "Database not available".to_string(),
            }),
        )
    })?;

    let releases = db::get_all_releases(db.pool()).await.map_err(|e| {
        tracing::error!("Database error: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to fetch releases".to_string(),
            }),
        )
    })?;

    Ok(Json(releases.into_iter().map(ReleaseInfo::from).collect()))
}

/// POST /api/releases - Create new release (admin only)
pub async fn create_release(
    State(state): State<AppState>,
    _admin: AdminUser,
    Json(request): Json<CreateReleaseRequest>,
) -> Result<Json<ReleaseInfo>, (StatusCode, Json<ErrorResponse>)> {
    let db = state.db.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ErrorResponse {
                error: "Database not available".to_string(),
            }),
        )
    })?;

    // Validate version format (basic check)
    if request.version.is_empty() {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(ErrorResponse {
                error: "Version cannot be empty".to_string(),
            }),
        ));
    }

    // Validate checksum format (64 hex chars for SHA-256)
    if request.checksum_sha256.len() != 64
        || !request
            .checksum_sha256
            .chars()
            .all(|c| c.is_ascii_hexdigit())
    {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(ErrorResponse {
                error: "Invalid SHA-256 checksum format (expected 64 hex characters)".to_string(),
            }),
        ));
    }

    // Validate URL
    if !request.download_url.starts_with("https://") {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(ErrorResponse {
                error: "Download URL must use HTTPS".to_string(),
            }),
        ));
    }

    // Check if version already exists
    if db::get_release_by_version(db.pool(), &request.version)
        .await
        .map_err(|e| {
            tracing::error!("Database error: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Database error".to_string(),
                }),
            )
        })?
        .is_some()
    {
        return Err((
            StatusCode::CONFLICT,
            Json(ErrorResponse {
                error: "Version already exists".to_string(),
            }),
        ));
    }

    let release = db::create_release(
        db.pool(),
        &request.version,
        &request.download_url,
        &request.checksum_sha256,
        request.release_notes.as_deref(),
        request.is_stable,
        request.is_mandatory,
        request.min_version.as_deref(),
    )
    .await
    .map_err(|e| {
        tracing::error!("Failed to create release: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to create release".to_string(),
            }),
        )
    })?;

    tracing::info!(
        "Created release: {} (stable={}, mandatory={})",
        release.version,
        release.is_stable,
        release.is_mandatory
    );

    Ok(Json(ReleaseInfo::from(release)))
}

/// GET /api/releases/:version - Get release by version (admin only)
pub async fn get_release(
    State(state): State<AppState>,
    _admin: AdminUser,
    Path(version): Path<String>,
) -> Result<Json<ReleaseInfo>, (StatusCode, Json<ErrorResponse>)> {
    let db = state.db.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ErrorResponse {
                error: "Database not available".to_string(),
            }),
        )
    })?;

    let release = db::get_release_by_version(db.pool(), &version)
        .await
        .map_err(|e| {
            tracing::error!("Database error: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Database error".to_string(),
                }),
            )
        })?
        .ok_or_else(|| {
            (
                StatusCode::NOT_FOUND,
                Json(ErrorResponse {
                    error: "Release not found".to_string(),
                }),
            )
        })?;

    Ok(Json(ReleaseInfo::from(release)))
}

/// PUT /api/releases/:version - Update release (admin only)
pub async fn update_release(
    State(state): State<AppState>,
    _admin: AdminUser,
    Path(version): Path<String>,
    Json(request): Json<UpdateReleaseRequest>,
) -> Result<Json<ReleaseInfo>, (StatusCode, Json<ErrorResponse>)> {
    let db = state.db.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ErrorResponse {
                error: "Database not available".to_string(),
            }),
        )
    })?;

    let release = db::update_release(
        db.pool(),
        &version,
        request.release_notes.as_deref(),
        request.is_stable,
        request.is_mandatory,
    )
    .await
    .map_err(|e| {
        tracing::error!("Database error: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to update release".to_string(),
            }),
        )
    })?
    .ok_or_else(|| {
        (
            StatusCode::NOT_FOUND,
            Json(ErrorResponse {
                error: "Release not found".to_string(),
            }),
        )
    })?;

    tracing::info!(
        "Updated release: {} (stable={}, mandatory={})",
        release.version,
        release.is_stable,
        release.is_mandatory
    );

    Ok(Json(ReleaseInfo::from(release)))
}

/// DELETE /api/releases/:version - Delete release (admin only)
pub async fn delete_release(
    State(state): State<AppState>,
    _admin: AdminUser,
    Path(version): Path<String>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
    let db = state.db.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ErrorResponse {
                error: "Database not available".to_string(),
            }),
        )
    })?;

    let deleted = db::delete_release(db.pool(), &version).await.map_err(|e| {
        tracing::error!("Database error: {}", e);
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to delete release".to_string(),
            }),
        )
    })?;

    if deleted {
        tracing::info!("Deleted release: {}", version);
        Ok(StatusCode::NO_CONTENT)
    } else {
        Err((
            StatusCode::NOT_FOUND,
            Json(ErrorResponse {
                error: "Release not found".to_string(),
            }),
        ))
    }
}
