//! Skill management API endpoints.

use actix_web::{HttpResponse, Result, web};
use service::AppService;
use session::Session;

use crate::{ApiResponse, error::ApiError};

#[derive(serde::Deserialize, utoipa::IntoParams)]
pub struct SkillPath {
    pub project_name: String,
    pub slug: String,
}

#[derive(Debug, serde::Deserialize, utoipa::IntoParams)]
pub struct SkillQuery {
    pub source: Option<String>,
    pub enabled: Option<bool>,
}

#[utoipa::path(
    get,
    path = "/api/projects/{project_name}/skills",
    params(
        ("project_name" = String, Path),
    ),
    responses(
        (status = 200, description = "List skills", body = ApiResponse<Vec<service::skill::info::SkillResponse>>),
        (status = 401, description = "Unauthorized"),
        (status = 404, description = "Project not found"),
    ),
    tag = "Skill"
)]
pub async fn skill_list(
    service: web::Data<AppService>,
    session: Session,
    path: web::Path<String>,
    query: web::Query<SkillQuery>,
) -> Result<HttpResponse, ApiError> {
    let project_name = path.into_inner();
    let project = service
        .project_info(&session, project_name.clone())
        .await?;

    let q = service::skill::info::SkillListQuery {
        source: query.source.clone(),
        enabled: query.enabled,
    };

    let skills = service
        .skill_list(project.uid.to_string(), q, &session)
        .await?;

    Ok(ApiResponse::ok(skills).to_response())
}

#[utoipa::path(
    get,
    path = "/api/projects/{project_name}/skills/{slug}",
    params(
        ("project_name" = String, Path),
        ("slug" = String, Path),
    ),
    responses(
        (status = 200, description = "Get skill", body = ApiResponse<service::skill::info::SkillResponse>),
        (status = 401, description = "Unauthorized"),
        (status = 404, description = "Not found"),
    ),
    tag = "Skill"
)]
pub async fn skill_get(
    service: web::Data<AppService>,
    session: Session,
    path: web::Path<SkillPath>,
) -> Result<HttpResponse, ApiError> {
    let SkillPath {
        project_name,
        slug,
    } = path.into_inner();

    let project = service
        .project_info(&session, project_name.clone())
        .await?;

    let skill = service
        .skill_get(project.uid.to_string(), slug, &session)
        .await?;

    Ok(ApiResponse::ok(skill).to_response())
}

#[utoipa::path(
    post,
    path = "/api/projects/{project_name}/skills",
    params(("project_name" = String, Path)),
    request_body = service::skill::manage::CreateSkillRequest,
    responses(
        (status = 200, description = "Create skill", body = ApiResponse<service::skill::info::SkillResponse>),
        (status = 401, description = "Unauthorized"),
        (status = 409, description = "Skill already exists"),
    ),
    tag = "Skill"
)]
pub async fn skill_create(
    service: web::Data<AppService>,
    session: Session,
    path: web::Path<String>,
    body: web::Json<service::skill::manage::CreateSkillRequest>,
) -> Result<HttpResponse, ApiError> {
    let project_name = path.into_inner();
    let project = service
        .project_info(&session, project_name.clone())
        .await?;

    let skill = service
        .skill_create(project.uid.to_string(), body.into_inner(), &session)
        .await?;

    Ok(ApiResponse::ok(skill).to_response())
}

#[utoipa::path(
    patch,
    path = "/api/projects/{project_name}/skills/{slug}",
    params(
        ("project_name" = String, Path),
        ("slug" = String, Path),
    ),
    request_body = service::skill::manage::UpdateSkillRequest,
    responses(
        (status = 200, description = "Update skill", body = ApiResponse<service::skill::info::SkillResponse>),
        (status = 401, description = "Unauthorized"),
        (status = 404, description = "Not found"),
    ),
    tag = "Skill"
)]
pub async fn skill_update(
    service: web::Data<AppService>,
    session: Session,
    path: web::Path<SkillPath>,
    body: web::Json<service::skill::manage::UpdateSkillRequest>,
) -> Result<HttpResponse, ApiError> {
    let SkillPath {
        project_name,
        slug,
    } = path.into_inner();

    let project = service
        .project_info(&session, project_name.clone())
        .await?;

    let skill = service
        .skill_update(project.uid.to_string(), slug, body.into_inner(), &session)
        .await?;

    Ok(ApiResponse::ok(skill).to_response())
}

#[utoipa::path(
    delete,
    path = "/api/projects/{project_name}/skills/{slug}",
    params(
        ("project_name" = String, Path),
        ("slug" = String, Path),
    ),
    responses(
        (status = 200, description = "Delete skill", body = ApiResponse<service::skill::manage::DeleteSkillResponse>),
        (status = 401, description = "Unauthorized"),
        (status = 404, description = "Not found"),
    ),
    tag = "Skill"
)]
pub async fn skill_delete(
    service: web::Data<AppService>,
    session: Session,
    path: web::Path<SkillPath>,
) -> Result<HttpResponse, ApiError> {
    let SkillPath {
        project_name,
        slug,
    } = path.into_inner();

    let project = service
        .project_info(&session, project_name.clone())
        .await?;

    let result = service
        .skill_delete(project.uid.to_string(), slug, &session)
        .await?;

    Ok(ApiResponse::ok(result).to_response())
}

#[utoipa::path(
    post,
    path = "/api/projects/{project_name}/skills/scan",
    params(("project_name" = String, Path)),
    responses(
        (status = 200, description = "Scan repos for skills", body = ApiResponse<ScanResponse>),
        (status = 401, description = "Unauthorized"),
    ),
    tag = "Skill"
)]
pub async fn skill_scan(
    service: web::Data<AppService>,
    session: Session,
    path: web::Path<String>,
) -> Result<HttpResponse, ApiError> {
    let project_name = path.into_inner();
    let project = service
        .project_info(&session, project_name)
        .await?;

    let result = service
        .skill_scan_repos(project.uid, project.uid)
        .await?;

    Ok(ApiResponse::ok(ScanResponse {
        discovered: result.discovered,
        created: result.created,
        updated: result.updated,
        removed: result.removed,
    }).to_response())
}

#[derive(serde::Serialize, utoipa::ToSchema)]
pub struct ScanResponse {
    pub discovered: i64,
    pub created: i64,
    pub updated: i64,
    pub removed: i64,
}

pub fn init_skill_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/projects/{project_name}/skills")
            .route("", web::get().to(skill_list))
            .route("", web::post().to(skill_create))
            .route("/scan", web::post().to(skill_scan))
            .route("/{slug}", web::get().to(skill_get))
            .route("/{slug}", web::patch().to(skill_update))
            .route("/{slug}", web::delete().to(skill_delete)),
    );
}
