use crate::extract::{Extractor, ParamFromStr};
use crate::EternaltwinSystem;
use axum::body::Body;
use axum::extract::Path;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::{Extension, Json, Router};
use eternaltwin_core::auth::AuthContext;
use eternaltwin_core::core::Listing;
use eternaltwin_core::job::{ApiTask, Job, JobId, OpaqueTask, TaskId, TaskKind};
use eternaltwin_services::job::{CreateJob, GetJob, GetJobs, GetTask};
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;

pub fn router() -> Router<(), Body> {
  Router::new()
    .route("/jobs", get(get_jobs).post(create_job))
    .route("/jobs/:job_id", get(get_job))
    .route("/tasks/:task_id", get(get_task))
}

#[derive(Debug, Error)]
enum GetJobsError {
  #[error("internal error")]
  Internal(#[from] eternaltwin_services::job::GetJobsError),
}

impl IntoResponse for GetJobsError {
  fn into_response(self) -> Response {
    let status = match &self {
      Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
    };
    (status, Json(json!({ "error": self.to_string() }))).into_response()
  }
}

async fn get_jobs(
  Extension(api): Extension<EternaltwinSystem>,
  acx: Extractor<AuthContext>,
) -> Result<Json<Listing<Job>>, GetJobsError> {
  Ok(Json(
    api
      .job
      .get_jobs(
        &acx.value(),
        &GetJobs {
          offset: 0,
          limit: 100,
          status: None,
          creator: None,
        },
      )
      .await?,
  ))
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct CreateJobBody {
  kind: TaskKind,
  state: OpaqueTask,
}

#[derive(Debug, Error)]
enum CreateJobError {
  #[error("current actor does not have the permission to create a job")]
  Forbidden,
  #[error("internal error")]
  Internal(#[source] eternaltwin_services::job::CreateJobError),
}

impl From<eternaltwin_services::job::CreateJobError> for CreateJobError {
  fn from(inner: eternaltwin_services::job::CreateJobError) -> Self {
    use eternaltwin_services::job::CreateJobError::*;
    match inner {
      Forbidden => Self::Forbidden,
      e => {
        eprintln!("CreateJobError: {e:#}");
        Self::Internal(e)
      }
    }
  }
}

impl IntoResponse for CreateJobError {
  fn into_response(self) -> Response {
    let status = match &self {
      Self::Forbidden => StatusCode::FORBIDDEN,
      Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
    };
    (status, Json(json!({ "error": self.to_string() }))).into_response()
  }
}

async fn create_job(
  Extension(api): Extension<EternaltwinSystem>,
  acx: Extractor<AuthContext>,
  Json(body): Json<CreateJobBody>,
) -> Result<Json<Job>, CreateJobError> {
  Ok(Json(
    api
      .job
      .create_job(
        &acx.value(),
        &CreateJob {
          kind: body.kind,
          kind_version: 1,
          state: body.state,
        },
      )
      .await?,
  ))
}

#[derive(Debug, Error)]
enum GetJobError {
  #[error("internal error")]
  Internal(#[from] eternaltwin_services::job::GetJobError),
}

impl IntoResponse for GetJobError {
  fn into_response(self) -> Response {
    let status = match &self {
      Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
    };
    (status, Json(json!({ "error": self.to_string() }))).into_response()
  }
}

async fn get_job(
  Extension(api): Extension<EternaltwinSystem>,
  acx: Extractor<AuthContext>,
  Path(ParamFromStr(job_id)): Path<ParamFromStr<JobId>>,
) -> Result<Json<Job>, GetJobError> {
  Ok(Json(api.job.get_job(&acx.value(), &GetJob { id: job_id }).await?))
}

#[derive(Debug, Error)]
enum GetTaskError {
  #[error("internal error")]
  Internal(#[from] eternaltwin_services::job::GetTaskError),
}

impl IntoResponse for GetTaskError {
  fn into_response(self) -> Response {
    let status = match &self {
      Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
    };
    (status, Json(json!({ "error": self.to_string() }))).into_response()
  }
}

async fn get_task(
  Extension(api): Extension<EternaltwinSystem>,
  acx: Extractor<AuthContext>,
  Path(ParamFromStr(task_id)): Path<ParamFromStr<TaskId>>,
) -> Result<Json<ApiTask>, GetTaskError> {
  Ok(Json(api.job.get_task(&acx.value(), &GetTask { id: task_id }).await?))
}
