use crate::api::v1::util::filters::IntoFilter;
use crate::db::Pool;
use crate::schema::{build_inputs, source_packages};
use crate::{attestation, web};
use actix_web::{HttpResponse, Responder, get};
use diesel::{QueryDsl, RunQueryDsl, SqliteExpressionMethods};
use in_toto::crypto::PrivateKey;
use rebuilderd_common::api::v1::FreshnessFilter;
use rebuilderd_common::errors::Error;
use serde_json::json;
use std::sync::Arc;

const NO_RELEASE: &str = "-";

fn derive_release(release: &str) -> Option<&str> {
    Some(release).filter(|r| *r != NO_RELEASE)
}

#[get("/distributions")]
pub async fn get_distributions(
    pool: web::Data<Pool>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;

    let distributions = source_packages::table
        .filter(freshness_filter.into_inner().into_filter())
        .select(source_packages::distribution)
        .distinct()
        .load::<String>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distributions))
}

#[get("/distributions/{distribution}/releases")]
pub async fn get_distribution_releases(
    pool: web::Data<Pool>,
    distribution: web::Path<String>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;

    let distribution_releases = source_packages::table
        .filter(source_packages::distribution.is(distribution.into_inner()))
        .filter(freshness_filter.into_inner().into_filter())
        .select(source_packages::release)
        .distinct()
        .load::<Option<String>>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distribution_releases))
}

#[get("/distributions/{distribution}/architectures")]
pub async fn get_distribution_architectures(
    pool: web::Data<Pool>,
    distribution: web::Path<String>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;

    let distribution_architectures = source_packages::table
        .inner_join(build_inputs::table)
        .filter(source_packages::distribution.is(distribution.into_inner()))
        .filter(freshness_filter.into_inner().into_filter())
        .select(build_inputs::architecture)
        .distinct()
        .load::<String>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distribution_architectures))
}

#[get("/distributions/{distribution}/components")]
pub async fn get_distribution_components(
    pool: web::Data<Pool>,
    distribution: web::Path<String>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;

    let distribution_components = source_packages::table
        .filter(source_packages::distribution.is(distribution.into_inner()))
        .filter(freshness_filter.into_inner().into_filter())
        .select(source_packages::component)
        .distinct()
        .load::<Option<String>>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distribution_components))
}

#[get("/distributions/{distribution}/releases/{release}/architectures")]
pub async fn get_distribution_release_architectures(
    pool: web::Data<Pool>,
    path: web::Path<(String, String)>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;
    let release = derive_release(&path.1);

    let distribution_release_architectures = source_packages::table
        .inner_join(build_inputs::table)
        .filter(source_packages::distribution.is(&path.0))
        .filter(source_packages::release.is(release))
        .filter(freshness_filter.into_inner().into_filter())
        .select(build_inputs::architecture)
        .distinct()
        .load::<String>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distribution_release_architectures))
}

#[get("/distributions/{distribution}/releases/{release}/components")]
pub async fn get_distribution_release_components(
    pool: web::Data<Pool>,
    path: web::Path<(String, String)>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;
    let release = derive_release(&path.1);

    let distribution_release_components = source_packages::table
        .filter(source_packages::distribution.is(&path.0))
        .filter(source_packages::release.is(release))
        .filter(freshness_filter.into_inner().into_filter())
        .select(source_packages::component)
        .distinct()
        .load::<Option<String>>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distribution_release_components))
}

#[get("/distributions/{distribution}/releases/{release}/components/{component}/architectures")]
pub async fn get_distribution_release_component_architectures(
    pool: web::Data<Pool>,
    path: web::Path<(String, String, String)>,
    freshness_filter: web::Query<FreshnessFilter>,
) -> web::Result<impl Responder> {
    let mut connection = pool.get().map_err(Error::from)?;
    let release = derive_release(&path.1);

    let distribution_release_component_architectures = source_packages::table
        .inner_join(build_inputs::table)
        .filter(source_packages::distribution.is(&path.0))
        .filter(source_packages::release.is(release))
        .filter(source_packages::component.is(&path.2))
        .filter(freshness_filter.into_inner().into_filter())
        .select(build_inputs::architecture)
        .distinct()
        .load::<String>(connection.as_mut())
        .map_err(Error::from)?;

    Ok(HttpResponse::Ok().json(distribution_release_component_architectures))
}

#[get("/public-keys")]
pub async fn get_public_key(
    private_key: web::Data<Arc<PrivateKey>>,
) -> web::Result<impl Responder> {
    let public_key = attestation::pubkey_to_pem(private_key.public())?;

    Ok(HttpResponse::Ok().json(json!({
        "current": vec![public_key],
    })))
}
