#![allow(clippy::unwrap_used, clippy::expect_used)]

use anyhow::Result;
use async_trait::async_trait;
use axum::{
    Router,
    body::Body,
    http::{Request, StatusCode, header},
    response::IntoResponse,
};
use serde_json::json;
use std::sync::Arc;
use toolkit::{
    ClientHub, Gear,
    api::OperationBuilder,
    api::operation_builder::LicenseFeature,
    config::ConfigProvider,
    context::GearCtx,
    contracts::{ApiGatewayCapability, OpenApiRegistry, RestApiCapability},
};
use toolkit_canonical_errors::Problem;
use tower::ServiceExt;
use uuid::Uuid;

const PERMISSION_DENIED_TYPE: &str =
    "gts://gts.cf.core.errors.err.v1~cf.core.err.permission_denied.v1~";
const PROBLEM_JSON: &str = "application/problem+json";

struct TestConfigProvider {
    config: serde_json::Value,
}

impl ConfigProvider for TestConfigProvider {
    fn get_gear_config(&self, gear: &str) -> Option<&serde_json::Value> {
        self.config.get(gear)
    }
}

fn create_api_gateway_ctx(config: serde_json::Value) -> GearCtx {
    let hub = Arc::new(ClientHub::new());

    GearCtx::new(
        "api-gateway",
        Uuid::new_v4(),
        Arc::new(TestConfigProvider { config }),
        hub,
        tokio_util::sync::CancellationToken::new(),
    )
}

fn create_test_gear_ctx() -> GearCtx {
    GearCtx::new(
        "test_gear",
        Uuid::new_v4(),
        Arc::new(TestConfigProvider { config: json!({}) }),
        Arc::new(ClientHub::new()),
        tokio_util::sync::CancellationToken::new(),
    )
}

async fn ok_handler() -> impl IntoResponse {
    StatusCode::OK
}

pub struct TestLicenseGear;

#[async_trait]
impl Gear for TestLicenseGear {
    async fn init(&self, _ctx: &GearCtx) -> Result<()> {
        Ok(())
    }
}

struct NonBaseFeature;

impl AsRef<str> for NonBaseFeature {
    fn as_ref(&self) -> &'static str {
        "some_other_feature"
    }
}

impl LicenseFeature for NonBaseFeature {}

struct BaseFeature;

impl AsRef<str> for BaseFeature {
    fn as_ref(&self) -> &'static str {
        "gts.cf.core.lic.feat.v1~cf.core.global.base.v1"
    }
}

impl LicenseFeature for BaseFeature {}

impl RestApiCapability for TestLicenseGear {
    fn register_rest(
        &self,
        _ctx: &GearCtx,
        router: Router,
        openapi: &dyn OpenApiRegistry,
    ) -> Result<Router> {
        let feature = NonBaseFeature;

        let router = OperationBuilder::get("/tests/v1/license/bad")
            .operation_id("test.license.bad")
            .authenticated()
            .require_license_features([&feature])
            .handler(ok_handler)
            .json_response(http::StatusCode::OK, "OK")
            .register(router, openapi);

        let base_feature = BaseFeature;

        let router = OperationBuilder::get("/tests/v1/license/good")
            .operation_id("test.license.good")
            .authenticated()
            .require_license_features([&base_feature])
            .handler(ok_handler)
            .json_response(http::StatusCode::OK, "OK")
            .register(router, openapi);

        let router = OperationBuilder::get("/tests/v1/license/none")
            .operation_id("test.license.none")
            .authenticated()
            .require_license_features::<BaseFeature>([])
            .handler(ok_handler)
            .json_response(http::StatusCode::OK, "OK")
            .register(router, openapi);

        Ok(router)
    }
}

#[tokio::test]
async fn rejects_non_base_feature_requirement() {
    let config = json!({
        "api-gateway": {
            "config": {
                "bind_addr": "0.0.0.0:8080",
                "enable_docs": false,
                "cors_enabled": false,
                "auth_disabled": true,
            }
        }
    });

    let api_ctx = create_api_gateway_ctx(config);
    let test_ctx = create_test_gear_ctx();

    let api_gateway = api_gateway::ApiGateway::default();
    api_gateway.init(&api_ctx).await.expect("Failed to init");

    let mut router = Router::new();
    let test_gear = TestLicenseGear;
    router = test_gear
        .register_rest(&test_ctx, router, &api_gateway)
        .expect("Failed to register routes");

    router = api_gateway
        .rest_finalize(&api_ctx, router)
        .expect("Failed to finalize");

    let response = router
        .oneshot(
            Request::builder()
                .uri("/tests/v1/license/bad")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .expect("Request failed");

    assert_eq!(response.status(), StatusCode::FORBIDDEN);
    assert_eq!(
        response
            .headers()
            .get(header::CONTENT_TYPE)
            .and_then(|v| v.to_str().ok())
            .unwrap_or(""),
        PROBLEM_JSON
    );
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
        .await
        .expect("read body");
    let problem: Problem = serde_json::from_slice(&body).expect("parse Problem JSON");
    assert_eq!(problem.problem_type, PERMISSION_DENIED_TYPE);
    assert_eq!(problem.context["reason"], "LICENSE_FEATURE_REQUIRED");
}

#[tokio::test]
async fn rejects_non_base_feature_requirement_with_prefix() {
    let config = json!({
        "api-gateway": {
            "config": {
                "bind_addr": "0.0.0.0:8080",
                "enable_docs": false,
                "cors_enabled": false,
                "auth_disabled": true,
                "prefix_path": "/cf",
            }
        }
    });

    let api_ctx = create_api_gateway_ctx(config);
    let test_ctx = create_test_gear_ctx();

    let api_gateway = api_gateway::ApiGateway::default();
    api_gateway.init(&api_ctx).await.expect("Failed to init");

    let mut router = Router::new();
    let test_gear = TestLicenseGear;
    router = test_gear
        .register_rest(&test_ctx, router, &api_gateway)
        .expect("Failed to register routes");

    router = api_gateway
        .rest_finalize(&api_ctx, router)
        .expect("Failed to finalize");

    let response = router
        .clone()
        .oneshot(
            Request::builder()
                .uri("/cf/tests/v1/license/bad")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .expect("Request failed");

    assert_eq!(response.status(), StatusCode::FORBIDDEN);

    let response = router
        .oneshot(
            Request::builder()
                .uri("/tests/v1/license/bad")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .expect("Request failed");

    assert_eq!(response.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn allows_base_feature_requirement() {
    let config = json!({
        "api-gateway": {
            "config": {
                "bind_addr": "0.0.0.0:8080",
                "enable_docs": false,
                "cors_enabled": false,
                "auth_disabled": true,
            }
        }
    });

    let api_ctx = create_api_gateway_ctx(config);
    let test_ctx = create_test_gear_ctx();

    let api_gateway = api_gateway::ApiGateway::default();
    api_gateway.init(&api_ctx).await.expect("Failed to init");

    let router = Router::new();
    let test_gear = TestLicenseGear;
    let router = test_gear
        .register_rest(&test_ctx, router, &api_gateway)
        .expect("Failed to register routes");

    let router = api_gateway
        .rest_finalize(&api_ctx, router)
        .expect("Failed to finalize");

    let response = router
        .oneshot(
            Request::builder()
                .uri("/tests/v1/license/good")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .expect("Request failed");

    assert_eq!(response.status(), StatusCode::OK);
}

#[tokio::test]
async fn allows_base_feature_requirement_with_prefix() {
    let config = json!({
        "api-gateway": {
            "config": {
                "bind_addr": "0.0.0.0:8080",
                "enable_docs": false,
                "cors_enabled": false,
                "auth_disabled": true,
                "prefix_path": "/cf",
            }
        }
    });

    let api_ctx = create_api_gateway_ctx(config);
    let test_ctx = create_test_gear_ctx();

    let api_gateway = api_gateway::ApiGateway::default();
    api_gateway.init(&api_ctx).await.expect("Failed to init");

    let router = Router::new();
    let test_gear = TestLicenseGear;
    let router = test_gear
        .register_rest(&test_ctx, router, &api_gateway)
        .expect("Failed to register routes");

    let router = api_gateway
        .rest_finalize(&api_ctx, router)
        .expect("Failed to finalize");

    let response = router
        .oneshot(
            Request::builder()
                .uri("/cf/tests/v1/license/good")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .expect("Request failed");

    assert_eq!(response.status(), StatusCode::OK);
}

#[tokio::test]
async fn allows_no_license_requirement() {
    let config = json!({
        "api-gateway": {
            "config": {
                "bind_addr": "0.0.0.0:8080",
                "enable_docs": false,
                "cors_enabled": false,
                "auth_disabled": true,
            }
        }
    });

    let api_ctx = create_api_gateway_ctx(config);
    let test_ctx = create_test_gear_ctx();

    let api_gateway = api_gateway::ApiGateway::default();
    api_gateway.init(&api_ctx).await.expect("Failed to init");

    let mut router = Router::new();
    let test_gear = TestLicenseGear;
    router = test_gear
        .register_rest(&test_ctx, router, &api_gateway)
        .expect("Failed to register routes");

    let router = api_gateway
        .rest_finalize(&api_ctx, router)
        .expect("Failed to finalize");

    let response = router
        .oneshot(
            Request::builder()
                .uri("/tests/v1/license/none")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .expect("Request failed");

    assert_eq!(response.status(), StatusCode::OK);
}
