use actix_web::{HttpResponse, web};
use serde::{Deserialize, Serialize};
use service::AppService;
use session::Session;

use crate::error::ApiError;
use crate::git::dto;

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 WkRepoBlobPath {
    pub wk: String,
    pub repo: String,
    pub oid: String,
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct BlobPathQuery {
    pub path: Option<String>,
}
#[derive(Serialize, utoipa::ToSchema)]
pub struct BlobInfoResponse {
    #[serde(flatten)]
    pub load: dto::BlobLoadResponseDto,
    pub size: u64,
    pub is_binary: bool,
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/blobs/{oid}",
    params(WkRepoBlobPath, BlobPathQuery),
    responses((status = 200, description = "Blob info", body = BlobInfoResponse)),
    security(("session" = []))
)]
pub async fn blob_info(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoBlobPath>,
    query: web::Query<BlobPathQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoBlobPath { wk, repo, oid } = path.into_inner();
    let path_opt = query.path.clone().unwrap_or_default();

    let load: dto::BlobLoadResponseDto = service
        .git_blob_load(&session, &wk, &repo, oid.clone(), path_opt.clone())
        .await?
        .into();
    let size_resp: dto::BlobSizeResponseDto = service
        .git_blob_size(&session, &wk, &repo, oid.clone(), path_opt)
        .await?
        .into();
    let binary_resp: dto::BlobIsBinaryResponseDto = service
        .git_blob_is_binary(&session, &wk, &repo, oid)
        .await?
        .into();

    ok_json(BlobInfoResponse {
        load,
        size: size_resp.size,
        is_binary: binary_resp.is_binary,
    })
}

#[derive(Deserialize, utoipa::ToSchema)]
pub struct BlobUploadBody {
    pub path: String,
    pub blob: Vec<u8>,
}
#[utoipa::path(
    post, path = "/api/v1/workspace/{wk}/repos/{repo}/git/blobs",
    params(WkRepoPath),
    request_body = BlobUploadBody,
    responses((status = 200, description = "Upload result", body = dto::BlobUploadResponseDto)),
    security(("session" = []))
)]
pub async fn blob_upload(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    params: web::Json<BlobUploadBody>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    let p = params.into_inner();
    let data: dto::BlobUploadResponseDto = service
        .git_blob_upload(&session, &wk, &repo, p.path, p.blob)
        .await?
        .into();
    ok_json(data)
}
