use actix_web::{HttpResponse, web};
use serde::{Deserialize, Serialize};
use service::AppService;
use service::git::contents::{ContentResponse, CreateContent, UpdateContent};
use session::Session;

use crate::error::ApiError;

fn ok_json<T: Serialize>(data: T) -> Result<HttpResponse, ApiError> {
    Ok(HttpResponse::Ok().json(data))
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct WkRepoPath {
    pub wk: String,
    pub repo: String,
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct ContentQuery {
    pub r#ref: Option<String>,
}

#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/contents/{path}",
    params(WkRepoPath, ("path" = String, Path), ContentQuery),
    responses((status = 200, body = ContentResponse)),
    security(("session" = []))
)]
pub async fn get_contents(
    session: Session,
    service: web::Data<AppService>,
    info: web::Path<(String, String, String)>,
    query: web::Query<ContentQuery>,
) -> Result<HttpResponse, ApiError> {
    let (wk, repo_name, file_path) = info.into_inner();
    ok_json(
        service
            .git_contents_get_by_name(
                &session,
                &wk,
                &repo_name,
                &file_path,
                query.r#ref.as_deref(),
            )
            .await?,
    )
}

#[utoipa::path(
    post, path = "/api/v1/workspace/{wk}/repos/{repo}/contents/{path}",
    params(WkRepoPath, ("path" = String, Path)),
    request_body = CreateContent,
    responses((status = 201, body = ContentResponse)),
    security(("session" = []))
)]
pub async fn create_contents(
    session: Session,
    service: web::Data<AppService>,
    info: web::Path<(String, String, String)>,
    body: web::Json<CreateContent>,
) -> Result<HttpResponse, ApiError> {
    let (wk, repo_name, file_path) = info.into_inner();
    let resp = service
        .git_contents_create_by_name(
            &session,
            &wk,
            &repo_name,
            &file_path,
            body.into_inner(),
        )
        .await?;
    Ok(HttpResponse::Created().json(resp))
}

#[utoipa::path(
    put, path = "/api/v1/workspace/{wk}/repos/{repo}/contents/{path}",
    params(WkRepoPath, ("path" = String, Path)),
    request_body = UpdateContent,
    responses((status = 200, body = ContentResponse)),
    security(("session" = []))
)]
pub async fn update_contents(
    session: Session,
    service: web::Data<AppService>,
    info: web::Path<(String, String, String)>,
    body: web::Json<UpdateContent>,
) -> Result<HttpResponse, ApiError> {
    let (wk, repo_name, file_path) = info.into_inner();
    ok_json(
        service
            .git_contents_update_by_name(
                &session,
                &wk,
                &repo_name,
                &file_path,
                body.into_inner(),
            )
            .await?,
    )
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct DeleteContentQuery {
    pub message: String,
    pub sha: String,
    pub branch: Option<String>,
}

#[utoipa::path(
    delete, path = "/api/v1/workspace/{wk}/repos/{repo}/contents/{path}",
    params(WkRepoPath, ("path" = String, Path), DeleteContentQuery),
    responses((status = 204)),
    security(("session" = []))
)]
pub async fn delete_contents(
    session: Session,
    service: web::Data<AppService>,
    info: web::Path<(String, String, String)>,
    query: web::Query<DeleteContentQuery>,
) -> Result<HttpResponse, ApiError> {
    let (wk, repo_name, file_path) = info.into_inner();
    service
        .git_contents_delete_by_name(
            &session,
            &wk,
            &repo_name,
            &file_path,
            &query.message,
            &query.sha,
            query.branch.as_deref(),
        )
        .await?;
    Ok(HttpResponse::NoContent().finish())
}
