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

#[derive(Deserialize, utoipa::IntoParams)]
pub struct TagListQuery {
    #[serde(default)]
    pub summary: bool,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct RenameTagBody {
    pub new_name: String,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateTagMessageBody {
    pub message: String,
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/tags",
    params(WkRepoPath, Pagination, TagListQuery),
    responses(
        (status = 200, description = "Tag list", body = dto::TagListResponseDto),
        (status = 200, description = "Tag summary", body = dto::TagSummaryResponseDto),
    ),
    security(("session" = []))
)]
pub async fn list_tags(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    pagination: web::Query<Pagination>,
    query: web::Query<TagListQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    if query.summary {
        let data: dto::TagSummaryResponseDto =
            service.git_tag_summary(&session, &wk, &repo).await?.into();
        return ok_json(data);
    }
    let data: dto::TagListResponseDto = service
        .git_tag_list(&session, &wk, &repo, pagination.into_inner())
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/tags/{name}",
    params(WkRepoTagPath),
    responses((status = 200, description = "Tag info", body = dto::TagInfoResponseDto)),
    security(("session" = []))
)]
pub async fn tag_info(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoTagPath>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoTagPath { wk, repo, name } = path.into_inner();
    let data: dto::TagInfoResponseDto = service
        .git_tag_info(&session, &wk, &repo, name)
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    post, path = "/api/v1/workspace/{wk}/repos/{repo}/git/tags",
    params(WkRepoPath),
    request_body = Object, description = "TagInitParams { name, oid, message, tagger, force }",
    responses((status = 200, description = "Tag created", body = dto::TagInitResponseDto)),
    security(("session" = []))
)]
pub async fn init_tag(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    params: web::Json<git::rpc::proto::TagInitParams>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    let data: dto::TagInitResponseDto = service
        .git_tag_init(&session, &wk, &repo, params.into_inner())
        .await?
        .into();
    ok_json(data)
}
#[utoipa::path(
    delete, path = "/api/v1/workspace/{wk}/repos/{repo}/git/tags/{name}",
    params(WkRepoTagPath),
    responses((status = 200, description = "Tag deleted")),
    security(("session" = []))
)]
pub async fn delete_tag(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoTagPath>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoTagPath { wk, repo, name } = path.into_inner();
    let params = git::rpc::proto::TagDeleteParams { name };
    let _ = service.git_tag_delete(&session, &wk, &repo, params).await?;
    ok_json(serde_json::json!({}))
}
#[utoipa::path(
    patch, path = "/api/v1/workspace/{wk}/repos/{repo}/git/tags/{name}",
    params(WkRepoTagPath),
    request_body = Object,
    responses(
        (status = 200, description = "Tag renamed"),
        (status = 200, description = "Tag message updated"),
    ),
    security(("session" = []))
)]
pub async fn update_tag(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoTagPath>,
    body: web::Json<serde_json::Value>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoTagPath { wk, repo, name } = path.into_inner();
    let body = body.into_inner();
    if let Some(new_name) = body.get("new_name").and_then(|v| v.as_str()) {
        let params = git::rpc::proto::TagRenameParams {
            old_name: name,
            new_name: new_name.to_string(),
            force: false,
        };
        let _ = service.git_tag_rename(&session, &wk, &repo, params).await?;
        return ok_json(serde_json::json!({}));
    }
    let message = body
        .get("message")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let params = git::rpc::proto::TagUpdateMessageParams {
        name,
        message,
        tagger: None,
        force: false,
    };
    let data: dto::TagUpdateMessageResponseDto = service
        .git_tag_update_message(&session, &wk, &repo, params)
        .await?
        .into();
    ok_json(data)
}
