pub mod blob;
pub mod branch;
pub mod cache_helper;
pub mod commit;
pub mod tag;
pub mod tree;

use std::path::PathBuf;

use actix_web::{HttpResponse, web};
use cache::AppCache;
use db::database::AppDatabase;
use juniper::{
    EmptyMutation, EmptySubscription, FieldResult, RootNode, graphql_object,
};
use serde_json::json;

use crate::{
    bare::GitBare,
    graphql::{
        blob::{BlobResult, resolve_blob, resolve_blob_size},
        branch::{
            BranchGql, BranchSummaryGql, resolve_branch,
            resolve_branch_summary, resolve_branches, resolve_head_branch,
        },
        commit::{
            CommitGql, CommitSummaryGql, resolve_commit,
            resolve_commit_history, resolve_commit_summary,
        },
        tag::{
            TagGql, TagSummaryGql, resolve_tag, resolve_tag_summary,
            resolve_tags,
        },
        tree::{TreeEntryGql, TreeInfoGql, resolve_tree, resolve_tree_entries},
    },
};

type Schema = RootNode<
    GraphqlQuery,
    EmptyMutation<GraphqlContext>,
    EmptySubscription<GraphqlContext>,
>;

fn schema() -> Schema {
    Schema::new(
        GraphqlQuery,
        EmptyMutation::<GraphqlContext>::new(),
        EmptySubscription::<GraphqlContext>::new(),
    )
}

pub async fn graphql_handle(
    path: web::Path<(String, String)>,
    state: web::Data<crate::http::HttpAppState>,
    body: web::Json<juniper::http::GraphQLRequest>,
) -> HttpResponse {
    let (wk, repo_name) = path.into_inner();
    let repo = match state.git_state.repo(wk, repo_name).await {
        Ok(repo) => repo,
        Err(err) => {
            return HttpResponse::InternalServerError().json(json!({
                "message": err.to_string()
            }));
        }
    };

    let ctx = GraphqlContext {
        repo: GitBare {
            bare_dir: PathBuf::from(&repo.repo.storage_path),
        },
        cache: state.git_state.cache.clone(),
        db: state.git_state.db.clone(),
    };

    let schema = schema();
    let response = body.execute(&schema, &ctx).await;

    let status_code = if response.is_ok() { 200 } else { 400 };
    HttpResponse::build(
        actix_web::http::StatusCode::from_u16(status_code).unwrap(),
    )
    .content_type("application/json")
    .json(response)
}

#[derive(Clone)]
pub struct GraphqlContext {
    pub repo: GitBare,
    pub cache: AppCache,
    pub db: AppDatabase,
}

impl juniper::Context for GraphqlContext {}

pub struct GraphqlQuery;

fn to_field_error(e: anyhow::Error) -> juniper::FieldError {
    juniper::FieldError::new(e.to_string(), juniper::Value::null())
}

#[graphql_object]
#[graphql(context = GraphqlContext)]
impl GraphqlQuery {
    fn api_version() -> &'static str {
        env!("CARGO_PKG_VERSION")
    }

    async fn head_branch(ctx: &GraphqlContext) -> FieldResult<BranchGql> {
        resolve_head_branch(ctx).await.map_err(to_field_error)
    }

    async fn branches(ctx: &GraphqlContext) -> FieldResult<Vec<BranchGql>> {
        resolve_branches(ctx).await.map_err(to_field_error)
    }

    async fn branch(
        ctx: &GraphqlContext,
        name: String,
    ) -> FieldResult<BranchGql> {
        resolve_branch(ctx, name).await.map_err(to_field_error)
    }

    async fn branch_summary(
        ctx: &GraphqlContext,
    ) -> FieldResult<BranchSummaryGql> {
        resolve_branch_summary(ctx).await.map_err(to_field_error)
    }

    async fn tags(ctx: &GraphqlContext) -> FieldResult<Vec<TagGql>> {
        resolve_tags(ctx).await.map_err(to_field_error)
    }

    async fn tag(ctx: &GraphqlContext, name: String) -> FieldResult<TagGql> {
        resolve_tag(ctx, name).await.map_err(to_field_error)
    }

    async fn tag_summary(ctx: &GraphqlContext) -> FieldResult<TagSummaryGql> {
        resolve_tag_summary(ctx).await.map_err(to_field_error)
    }

    async fn commit(
        ctx: &GraphqlContext,
        oid: String,
    ) -> FieldResult<CommitGql> {
        resolve_commit(ctx, oid).await.map_err(to_field_error)
    }

    async fn commit_history(
        ctx: &GraphqlContext,
        limit: Option<i32>,
        skip: Option<i32>,
        sort: Option<String>,
    ) -> FieldResult<Vec<CommitGql>> {
        resolve_commit_history(ctx, limit, skip, sort)
            .await
            .map_err(to_field_error)
    }

    async fn commit_summary(
        ctx: &GraphqlContext,
    ) -> FieldResult<CommitSummaryGql> {
        resolve_commit_summary(ctx).await.map_err(to_field_error)
    }

    async fn tree(
        ctx: &GraphqlContext,
        oid: String,
    ) -> FieldResult<TreeInfoGql> {
        resolve_tree(ctx, oid).await.map_err(to_field_error)
    }

    async fn tree_entries(
        ctx: &GraphqlContext,
        tree_oid: String,
    ) -> FieldResult<Vec<TreeEntryGql>> {
        resolve_tree_entries(ctx, tree_oid)
            .await
            .map_err(to_field_error)
    }

    async fn blob_size(ctx: &GraphqlContext, oid: String) -> FieldResult<i32> {
        resolve_blob_size(ctx, oid).await.map_err(to_field_error)
    }

    async fn blob(
        ctx: &GraphqlContext,
        oid: String,
    ) -> FieldResult<BlobResult> {
        resolve_blob(ctx, oid).await.map_err(to_field_error)
    }
}
