use actix_web::{HttpResponse, web};
use serde::{Deserialize, Serialize};
use service::{AppService, Pagination};
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 WkRepoBranchPath {
    pub wk: String,
    pub repo: String,
    pub name: String,
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct BranchDeleteQuery {
    #[serde(default)]
    pub force: bool,
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct BranchListQuery {
    #[serde(default)]
    pub summary: bool,
    #[serde(default)]
    pub default_only: bool,
}

#[derive(Deserialize, utoipa::ToSchema)]
pub struct RenameBranchBody {
    pub new_branch: String,
    #[serde(default)]
    pub force: bool,
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches",
    params(WkRepoPath, Pagination, BranchListQuery),
    responses((status = 200, description = "Branch list or summary", body = dto::BranchListResponseDto)),
    security(("session" = []))
)]
pub async fn list_branches(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    pagination: web::Query<Pagination>,
    query: web::Query<BranchListQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    if query.summary {
        let data: dto::BranchSummaryResponseDto = service
            .git_branch_summary(&session, &wk, &repo)
            .await?
            .into();
        return ok_json(data);
    }
    if query.default_only {
        let data: dto::BranchHeadResponseDto =
            service.git_branch_head(&session, &wk, &repo).await?.into();
        return ok_json(data);
    }
    let data: dto::BranchListResponseDto = service
        .git_branch_list(&session, &wk, &repo, pagination.into_inner())
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches/{name}",
    params(WkRepoBranchPath),
    responses((status = 200, description = "Branch info", body = dto::BranchInfoResponseDto)),
    security(("session" = []))
)]
pub async fn branch_info(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoBranchPath>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoBranchPath { wk, repo, name } = path.into_inner();
    let data: dto::BranchInfoResponseDto = service
        .git_branch_info(&session, &wk, &repo, name)
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    post, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches",
    params(WkRepoPath),
    request_body = Object, description = "BranchForkParams { name, oid, force }",
    responses((status = 200, description = "Branch created")),
    security(("session" = []))
)]
pub async fn fork_branch(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    params: web::Json<git::rpc::proto::BranchForkParams>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    let data = service
        .git_branch_fork(&session, &wk, &repo, params.into_inner())
        .await?;
    ok_json(data)
}
#[utoipa::path(
    patch, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches/{name}",
    params(WkRepoBranchPath),
    request_body = RenameBranchBody,
    responses((status = 200, description = "Branch renamed")),
    security(("session" = []))
)]
pub async fn rename_branch(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoBranchPath>,
    body: web::Json<RenameBranchBody>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoBranchPath { wk, repo, name } = path.into_inner();
    let body = body.into_inner();
    let params = git::rpc::proto::BranchReNameParams {
        old_branch: name,
        new_branch: body.new_branch,
        force: body.force,
    };
    let data = service
        .git_branch_rename(&session, &wk, &repo, params)
        .await?;
    ok_json(data)
}
#[utoipa::path(
    delete, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches/{name}",
    params(WkRepoBranchPath, BranchDeleteQuery),
    responses((status = 200, description = "Branch deleted")),
    security(("session" = []))
)]
pub async fn delete_branch(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoBranchPath>,
    query: web::Query<BranchDeleteQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoBranchPath { wk, repo, name } = path.into_inner();
    let params = git::rpc::proto::BranchDeleteParams {
        name,
        force: query.force,
    };
    let data = service
        .git_branch_delete(&session, &wk, &repo, params)
        .await?;
    ok_json(data)
}

#[derive(Deserialize, utoipa::IntoParams)]
pub struct AheadBehindQuery {
    pub remote_branch: String,
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches/{name}/ahead-behind",
    params(WkRepoBranchPath, AheadBehindQuery),
    responses((status = 200, description = "Ahead/behind counts", body = dto::BranchAheadBehindResponseDto)),
    security(("session" = []))
)]
pub async fn ahead_behind(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoBranchPath>,
    query: web::Query<AheadBehindQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoBranchPath { wk, repo, name } = path.into_inner();
    let data: dto::BranchAheadBehindResponseDto = service
        .git_branch_ahead_behind(
            &session,
            &wk,
            &repo,
            name,
            query.remote_branch.clone(),
        )
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/branches/{name}/upstream",
    params(WkRepoBranchPath),
    responses((status = 200, description = "Upstream branch", body = dto::BranchUpstreamResponseDto)),
    security(("session" = []))
)]
pub async fn branch_upstream(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoBranchPath>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoBranchPath { wk, repo, name } = path.into_inner();
    let data: dto::BranchUpstreamResponseDto = service
        .git_branch_upstream(&session, &wk, &repo, name)
        .await?
        .into();
    ok_json(data)
}
