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 DiffQuery {
    pub old_oid: Option<String>,
    pub new_oid: Option<String>,
    pub old_tree: Option<String>,
    pub new_tree: Option<String>,
    pub tree_oid: Option<String>,
    #[serde(default = "default_mode")]
    pub mode: String,
    pub path: Option<String>,
}

fn default_mode() -> String {
    "patch".to_string()
}
#[utoipa::path(
    get, path = "/api/v1/workspace/{wk}/repos/{repo}/git/diff",
    params(WkRepoPath, DiffQuery),
    responses((status = 200, description = "Diff result")),
    security(("session" = []))
)]
pub async fn diff(
    session: Session,
    service: web::Data<AppService>,
    path: web::Path<WkRepoPath>,
    query: web::Query<DiffQuery>,
) -> Result<HttpResponse, ApiError> {
    let WkRepoPath { wk, repo } = path.into_inner();
    if let (Some(old_tree), Some(new_tree)) = (&query.old_tree, &query.new_tree)
    {
        let proto_resp = service
            .git_diff_tree_to_tree(
                &session,
                &wk,
                &repo,
                old_tree.clone(),
                new_tree.clone(),
                None,
            )
            .await?;
        let data: dto::DiffResultDto =
            proto_resp.result.unwrap_or_default().into();
        return ok_json(data);
    }
    if let Some(tree_oid) = &query.tree_oid {
        let proto_resp = service
            .git_diff_index_to_tree(
                &session,
                &wk,
                &repo,
                tree_oid.clone(),
                None,
            )
            .await?;
        let data: dto::DiffResultDto =
            proto_resp.result.unwrap_or_default().into();
        return ok_json(data);
    }
    let old_oid = query.old_oid.clone().unwrap_or_default();
    let new_oid = query.new_oid.clone().unwrap_or_default();

    match query.mode.as_str() {
        "stats" => {
            let proto_resp = service
                .git_diff_stats(&session, &wk, &repo, old_oid, new_oid, None)
                .await?;
            let data: dto::DiffStatsDto = proto_resp
                .result
                .and_then(|r| r.stats)
                .unwrap_or_default()
                .into();
            ok_json(data)
        }
        "side-by-side" => {
            let proto_resp = service
                .git_diff_patch_side_by_side(
                    &session, &wk, &repo, old_oid, new_oid, None,
                )
                .await?;
            let data: dto::SideBySideDiffResultDto =
                proto_resp.result.unwrap_or_default().into();
            ok_json(data)
        }
        _ => {
            let proto_resp = service
                .git_diff_patch(&session, &wk, &repo, old_oid, new_oid, None)
                .await?;
            let data: dto::DiffResultDto =
                proto_resp.result.unwrap_or_default().into();
            ok_json(data)
        }
    }
}
