use actix_web::{HttpResponse, web};
use git::rpc::proto as p;
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 WkRepoCommitPath {
    pub wk: String,
    pub repo: String,
    pub oid: String,
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct HistoryQuery {
    pub limit: Option<u64>,
    pub skip: Option<u64>,
    pub sort: Option<i32>,
    pub branch: Option<String>,
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct CommitListQuery {
    #[serde(default)]
    pub summary: bool,
    #[serde(default)]
    pub refs: bool,
    pub prefix: Option<String>,
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/commits",
    params(WkRepoPath, CommitListQuery),
    responses(
        (status = 200, description = "Commit list / summary / refs / prefix", body = dto::CommitHistoryResponseDto),
    ),
    security(("session" = []))
)]
pub async fn list_commits(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    query: web::Query<CommitListQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();

    if let Some(prefix) = &query.prefix {
        let data: dto::CommitPrefixResponseDto = service
            .git_commit_prefix(&session, &wk, &repo, prefix.clone())
            .await?
            .into();
        return ok_json(data);
    }
    if query.refs {
        let data: dto::CommitRefsResponseDto =
            service.git_commit_refs(&session, &wk, &repo).await?.into();
        return ok_json(data);
    }
    if query.summary {
        let data: dto::CommitSummaryResponseDto = service
            .git_commit_summary(&session, &wk, &repo)
            .await?
            .into();
        return ok_json(data);
    }
    let data: dto::CommitHistoryResponseDto = service
        .git_commit_history(&session, &wk, &repo, 20, 0, 0, None)
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/commits/history",
    params(WkRepoPath, HistoryQuery),
    responses((status = 200, description = "Commit history", body = dto::CommitHistoryResponseDto)),
    security(("session" = []))
)]
pub async fn commit_history(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    query: web::Query<HistoryQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    let data: dto::CommitHistoryResponseDto = service
        .git_commit_history(
            &session,
            &wk,
            &repo,
            query.limit.unwrap_or(20),
            query.skip.unwrap_or(0),
            query.sort.unwrap_or(0),
            query.branch.clone(),
        )
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/commits/{oid}",
    params(WkRepoCommitPath),
    responses((status = 200, description = "Commit info", body = dto::CommitInfoResponseDto)),
    security(("session" = []))
)]
pub async fn commit_info(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoCommitPath>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoCommitPath { wk, repo, oid } = path.into_inner();
    let data: dto::CommitInfoResponseDto = service
        .git_commit_info(&session, &wk, &repo, oid)
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    post, path = "/api/v1/workspace/{wk}/repos/{repo}/git/commits/walk",
    params(WkRepoPath),
    request_body = Object, description = "CommitWalkParams",
    responses((status = 200, description = "Walk result", body = dto::CommitHistoryResponseDto)),
    security(("session" = []))
)]
pub async fn commit_walk(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    params: web::Json<p::CommitWalkParams>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    let proto_resp = service
        .git_commit_walk(&session, &wk, &repo, params.into_inner())
        .await?;
    ok_json(dto::CommitHistoryResponseDto {
        commits: proto_resp.commits.into_iter().map(Into::into).collect(),
    })
}
#[utoipa::path(
    post, path = "/api/v1/workspace/{wk}/repos/{repo}/git/commits/cherry-pick",
    params(WkRepoPath),
    request_body = Object, description = "CommitCherryPickParams",
    responses((status = 200, description = "Cherry-pick result", body = dto::CherryPickResponseDto)),
    security(("session" = []))
)]
pub async fn cherry_pick(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    params: web::Json<p::CommitCherryPickParams>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    let data: dto::CherryPickResponseDto = service
        .git_cherry_pick(&session, &wk, &repo, params.into_inner())
        .await?
        .into();
    ok_json(data)
}
