use crate::permissions::Perm;
use crate::Config;
use axum::{
    extract::State,
    response::{IntoResponse, Redirect, Response},
    routing::{get, post},
    Form, Router,
};
use axum_extra::extract::SignedCookieJar;
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use serde_derive::*;
use tracing::*;
mod get;
mod list;
use diesel::ExpressionMethods;
use libpijul;
use libpijul::Base32;

pub fn router() -> Router<Config> {
    Router::new()
        .route("/{owner}/{repo}/list", get(list::changelist))
        .route("/{owner}/{repo}/get/{hash}", get(get::change))
        .route("/{owner}/{repo}/unrecord", post(unrecord))
        .fallback(crate::fallback_json)
}

#[derive(Debug, Clone)]
pub struct ChangeAuthor {
    pub login: String,
}

pub async fn change_author(
    db: &crate::config::Db,
    author: &libpijul::change::Author,
) -> Result<ChangeAuthor, crate::Error> {
    debug!("change_author: {:?}", author);
    use crate::db::users::dsl as u;
    if let Some(key) = author.0.get("key") {
        let keyb = bs58::decode(key.as_bytes()).into_vec();
        if let Ok(keyb) = keyb {
            use crate::db::signingkeys::dsl as sk;
            if let Some(login) = u::users
                .inner_join(sk::signingkeys)
                .filter(sk::public_key.eq(keyb))
                .filter(u::is_active)
                .select(u::login)
                .get_result(&mut db.get().await?)
                .await
                .optional()?
            {
                return Ok(ChangeAuthor {
                    login,
                });
            }
        }
        Ok(ChangeAuthor {
            login: key.to_string(),
        })
    } else if let Some(name) = author.0.get("name") {
        Ok(ChangeAuthor {
            login: name.to_string(),
        })
    } else {
        Ok(ChangeAuthor {
            login: "?".to_string(),
        })
    }
}

pub async fn authors_string(
    db: &crate::config::Db,
    authors: &[libpijul::change::Author],
) -> Result<Vec<String>, crate::Error> {
    let mut result = Vec::new();
    for a in authors {
        result.push(change_author(db, &a).await?.login)
    }
    Ok(result)
}

#[derive(Debug, Default, Serialize, Clone)]
pub struct Author {
    #[serde(skip_serializing_if = "Option::is_none")]
    login: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,
    key: String,
}

pub async fn get_authors(
    db: &mut diesel_async::AsyncPgConnection,
    authors: &mut [libpijul::change::Author],
) -> Result<Vec<Author>, diesel::result::Error> {
    let mut auth = Vec::with_capacity(authors.len());
    for a in authors.iter_mut() {
        if let Some(key) = a.0.remove("key") {
            use crate::db::signingkeys::dsl as k;
            use crate::db::users::dsl as u;
            if let Some((login, name)) = k::signingkeys
                .find(&bs58::decode(&key).into_vec().unwrap())
                .inner_join(u::users)
                .select((u::login, u::name))
                .get_result::<(String, Option<String>)>(db)
                .await
                .optional()?
            {
                auth.push(Author {
                    key,
                    login: Some(login),
                    name,
                })
            } else {
                auth.push(Author {
                    key,
                    ..Author::default()
                })
            }
        }
    }
    Ok(auth)
}

#[derive(Debug, Deserialize)]
pub struct UnrecordForm {
    token: String,
    hash: String,
}

async fn unrecord(
    State(config): State<Config>,
    jar: SignedCookieJar,
    token: axum_csrf::CsrfToken,
    repo: crate::repository::RepoPath,
    Form(form): Form<UnrecordForm>,
) -> Result<Response, crate::Error> {
    token.verify(&form.token)?;
    let uid = crate::get_user_id_strict(&jar)?;
    let mut db = config.db.get().await.unwrap();
    let (id, _) = crate::repository::repository_id(&mut db, &repo.owner, &repo.repo, Some(uid), Perm::APPLY)
        .await?;
    config
        .replicator
        .handle_update(
            None,
            None,
            None,
            ::replication::Update::Unrecord {
                repo: id,
                channel: repo.channel.unwrap_or_else(|| "main".to_string()),
                hash: if let Some(h) = libpijul::Hash::from_base32(form.hash.as_bytes()) {
                    h
                } else {
                    return Err(crate::Error::HashParse { hash: form.hash });
                },
                deps: config.replicator.deps(id).await?,
            },
        )
        .await?;
    Ok(Redirect::to(&format!("/{}/{}/change", repo.owner, repo.repo)).into_response())
}