//! REST handlers for the Types Registry gear.

use std::sync::Arc;

use axum::Json;
use axum::extract::{Extension, Path, Query};
use toolkit::api::canonical_prelude::*;

use super::dto::{
    GtsEntityDto, ListEntitiesQuery, ListEntitiesResponse, RegisterEntitiesRequest,
    RegisterEntitiesResponse, RegisterResultDto, RegisterSummaryDto,
};
use crate::domain::error::DomainError;
use crate::domain::service::TypesRegistryService;

/// POST /api/v1/types-registry/entities
///
/// Register GTS entities in batch.
/// REST API always validates entities, regardless of ready state.
/// However, REST API is blocked until service is ready.
pub async fn register_entities(
    Extension(service): Extension<Arc<TypesRegistryService>>,
    Json(req): Json<RegisterEntitiesRequest>,
) -> ApiResult<(StatusCode, Json<RegisterEntitiesResponse>)> {
    if !service.is_ready() {
        return Err(DomainError::NotInReadyMode.into());
    }

    let outcomes = service.register_validated(req.entities);

    let total = outcomes.len();
    let mut succeeded = 0_usize;
    let mut result_dtos: Vec<RegisterResultDto> = Vec::with_capacity(total);
    for (gts_id, outcome) in outcomes {
        match outcome {
            Ok(entity) => {
                succeeded += 1;
                result_dtos.push(RegisterResultDto::Ok {
                    entity: entity.into(),
                });
            }
            Err(e) => result_dtos.push(RegisterResultDto::Error {
                gts_id,
                error: e.to_string(),
            }),
        }
    }
    let failed = total - succeeded;

    let response = RegisterEntitiesResponse {
        summary: RegisterSummaryDto {
            total,
            succeeded,
            failed,
        },
        results: result_dtos,
    };

    Ok((StatusCode::OK, Json(response)))
}

/// GET /api/v1/types-registry/entities
///
/// List GTS entities with optional filtering.
pub async fn list_entities(
    Extension(service): Extension<Arc<TypesRegistryService>>,
    Query(query): Query<ListEntitiesQuery>,
) -> ApiResult<Json<ListEntitiesResponse>> {
    if !service.is_ready() {
        return Err(DomainError::NotInReadyMode.into());
    }

    let list_query = query.to_list_query();

    let entities = service.list(&list_query).map_err(CanonicalError::from)?;

    let entity_dtos: Vec<GtsEntityDto> = entities.into_iter().map(Into::into).collect();
    let count = entity_dtos.len();

    Ok(Json(ListEntitiesResponse {
        entities: entity_dtos,
        count,
    }))
}

/// GET /api/v1/types-registry/entities/{gts_id}
///
/// Get a single GTS entity by its identifier.
pub async fn get_entity(
    Extension(service): Extension<Arc<TypesRegistryService>>,
    Path(gts_id): Path<String>,
) -> ApiResult<Json<GtsEntityDto>> {
    if !service.is_ready() {
        return Err(DomainError::NotInReadyMode.into());
    }

    let entity = service.get(&gts_id).map_err(CanonicalError::from)?;

    Ok(Json(entity.into()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::infra::InMemoryGtsRepository;
    use gts::GtsConfig;
    use serde_json::json;

    const JSON_SCHEMA_DRAFT_07: &str = "http://json-schema.org/draft-07/schema#";

    fn default_config() -> GtsConfig {
        crate::config::TypesRegistryConfig::default().to_gts_config()
    }

    fn create_service() -> Arc<TypesRegistryService> {
        let repo = Arc::new(InMemoryGtsRepository::new(default_config()));
        Arc::new(TypesRegistryService::new(
            repo,
            crate::config::TypesRegistryConfig::default(),
        ))
    }

    #[tokio::test]
    async fn test_register_entities_returns_503_when_not_ready() {
        let service = create_service();
        // Service is not ready yet

        let req = RegisterEntitiesRequest {
            entities: vec![json!({
                "$id": "gts://gts.acme.core.events.user_created.v1~",
                "$schema": JSON_SCHEMA_DRAFT_07,
                "type": "object"
            })],
        };

        let result = register_entities(Extension(service), Json(req)).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_list_entities_returns_503_when_not_ready() {
        let service = create_service();
        // Service is not ready yet

        let query = ListEntitiesQuery::default();
        let result = list_entities(Extension(service), Query(query)).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_get_entity_returns_503_when_not_ready() {
        let service = create_service();
        // Service is not ready yet

        let result = get_entity(
            Extension(service),
            Path("gts.acme.core.events.user_created.v1~".to_owned()),
        )
        .await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_register_entities_handler_when_ready() {
        let service = create_service();
        service.switch_to_ready().unwrap();

        let req = RegisterEntitiesRequest {
            entities: vec![json!({
                "$id": "gts://gts.acme.core.events.user_created.v1~",
                "$schema": JSON_SCHEMA_DRAFT_07,
                "type": "object"
            })],
        };

        let result = register_entities(Extension(service), Json(req)).await;
        assert!(result.is_ok());

        let (status, Json(response)) = result.unwrap();
        assert_eq!(status, StatusCode::OK);
        assert_eq!(response.summary.total, 1);
        assert_eq!(response.summary.succeeded, 1);
        assert_eq!(response.summary.failed, 0);
    }

    #[tokio::test]
    async fn test_list_entities_handler_when_ready() {
        let service = create_service();

        // Register entities via internal API (before ready)
        _ = service.register(vec![
            json!({
                "$id": "gts://gts.acme.core.events.user_created.v1~",
                "$schema": JSON_SCHEMA_DRAFT_07,
                "type": "object"
            }),
            json!({
                "$id": "gts://gts.globex.core.events.order_placed.v1~",
                "$schema": JSON_SCHEMA_DRAFT_07,
                "type": "object"
            }),
        ]);
        service.switch_to_ready().unwrap();

        let query = ListEntitiesQuery::default();
        let result = list_entities(Extension(service), Query(query)).await;
        assert!(result.is_ok());

        let Json(response) = result.unwrap();
        assert_eq!(response.count, 2);
    }

    #[tokio::test]
    async fn test_get_entity_handler_when_ready() {
        let service = create_service();

        // Register entity via internal API (before ready)
        _ = service.register(vec![json!({
            "$id": "gts://gts.acme.core.events.user_created.v1~",
            "$schema": JSON_SCHEMA_DRAFT_07,
            "type": "object"
        })]);
        service.switch_to_ready().unwrap();

        let result = get_entity(
            Extension(service),
            Path("gts.acme.core.events.user_created.v1~".to_owned()),
        )
        .await;
        assert!(result.is_ok());

        let Json(entity) = result.unwrap();
        assert_eq!(entity.gts_id, "gts.acme.core.events.user_created.v1~");
    }

    #[tokio::test]
    async fn test_get_entity_not_found() {
        let service = create_service();
        service.switch_to_ready().unwrap();

        let result = get_entity(
            Extension(service),
            Path("gts.fabrikam.pkg.ns.type.v1~".to_owned()),
        )
        .await;
        assert!(result.is_err());
    }
}
