// SPDX-FileCopyrightText: Politik im Blick developers
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
//
// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2

use std::sync::Arc;

use axum::{Router, http::Method, routing::MethodRouter};
use http::header::{
    ACCEPT_ENCODING, AUTHORIZATION, CONNECTION, CONTENT_LENGTH, CONTENT_TYPE, DNT, HOST, ORIGIN,
    REFERER,
};
use pib_service_facade::Service;
use tower_http::{
    auth::AsyncRequireAuthorizationLayer,
    cors::{AllowOrigin, CorsLayer},
};

use crate::Auth;

mod body;
mod config;
mod get;
mod me;
mod user;

pub(super) async fn router<E: Into<pib_service_facade::Error> + Send + Sync + 'static>(
    auth: Auth,
    service: Arc<dyn Service<E>>,
) -> Router
where
    crate::Error: From<E>,
{
    let cors = CorsLayer::new()
        .allow_methods([
            Method::GET,
            Method::POST,
            Method::PUT,
            Method::DELETE,
            Method::PATCH,
        ])
        .allow_headers([
            AUTHORIZATION,
            CONTENT_TYPE,
            ACCEPT_ENCODING,
            CONTENT_LENGTH,
            ORIGIN,
            REFERER,
            CONNECTION,
            DNT,
            HOST,
        ])
        .allow_origin(AllowOrigin::mirror_request());

    let public = Router::new()
        .route("/", MethodRouter::new().get(get::handle))
        .route("/config", MethodRouter::new().get(config::get::handle));
    let private = Router::new()
        .route(
            "/me/profile",
            MethodRouter::new()
                .get(me::profile::get::handle)
                .patch(me::profile::patch::handle),
        )
        .route("/user", MethodRouter::new().get(user::get::handle))
        .route(
            "/body",
            MethodRouter::new()
                .get(body::get::handle)
                .post(body::post::handle),
        )
        .route(
            "/body/{body_id}",
            MethodRouter::new()
                .get(body::by_id::get::handle)
                .patch(body::by_id::patch::handle),
        )
        .route(
            "/body/{body_id}/permission",
            MethodRouter::new()
                .get(body::by_id::permission::get::handle)
                .post(body::by_id::permission::post::handle),
        )
        .route(
            "/body/{body_id}/organization",
            MethodRouter::new()
                .get(body::by_id::organization::get::handle)
                .post(body::by_id::organization::post::handle),
        )
        .route(
            "/body/{body_id}/meeting",
            MethodRouter::new()
                .get(body::by_id::meeting::get::handle)
                .post(body::by_id::meeting::post::handle),
        )
        .route(
            "/body/{body_id}/meeting/{meeting_id}",
            MethodRouter::new().get(body::by_id::meeting::by_id::get::handle),
        )
        .layer(AsyncRequireAuthorizationLayer::new(auth));
    Router::new()
        .merge(public)
        .merge(private)
        .layer(cors)
        .with_state(service)
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use axum_test::TestServer;
    use mockall::predicate::eq;

    use pib_service_api_auth::{
        MockApiAuth,
        user::{Issuer, OidcSub, UserInfo},
    };
    use pib_service_api_types::{self as types, config::oidc::OidcConfig, user::User};
    use pib_service_core_types::{BodyId, MeetingId, OrganizationId, UserId};
    use pib_service_facade::MockService;

    use crate::auth::Auth;

    use super::router;

    fn example_user_info() -> UserInfo {
        UserInfo {
            id: UserId::from_u128(0x1111),
            issuer: Issuer("https://auth.example.com/".to_string()),
            sub: OidcSub("max".to_string()),
            display_name: None,
            is_superuser: false,
        }
    }

    #[tokio::test]
    async fn success_get() {
        let api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        service
            .expect_handle_get()
            .times(1)
            .return_once(|| Ok(types::get::ResponseBody {}));

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/")
            .await
            .assert_status_success()
            .json::<types::get::ResponseBody>();

        assert_eq!(response, types::get::ResponseBody {})
    }

    #[tokio::test]
    async fn success_config_get() {
        let api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        let oidc = OidcConfig {
            issuer: "https://auth.example.com/".to_string(),
            client_id: "the-client-id".to_string(),
            client_secret: "the-client-secret".to_string(),
        };

        {
            let oidc = oidc.clone();
            service
                .expect_handle_config_get()
                .times(1)
                .return_once(|| Ok(types::config::get::ResponseBody { oidc }));
        }

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/config")
            .await
            .assert_status_success()
            .json::<types::config::get::ResponseBody>();

        assert_eq!(response, types::config::get::ResponseBody { oidc })
    }

    #[tokio::test]
    async fn success_me_profile_get() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        service
            .expect_handle_profile_me_get()
            .times(1)
            .return_once(|_| {
                Ok(types::me::profile::get::ResponseBody {
                    display_name: Some("Max".to_string()),
                })
            });

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/me/profile")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_success()
            .json::<types::me::profile::get::ResponseBody>();

        assert_eq!(
            response,
            types::me::profile::get::ResponseBody {
                display_name: Some("Max".to_string())
            }
        )
    }

    #[tokio::test]
    async fn success_me_profile_patch() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        service
            .expect_handle_profile_me_patch()
            .times(1)
            .return_once(|_, _| Ok(()));

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        server
            .patch("/me/profile")
            .json(&types::me::profile::patch::RequestBody {
                display_name: Some(Some("hello".to_string())),
            })
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_see_other()
            .assert_header("Location", "/me/profile");
    }

    #[tokio::test]
    async fn success_user_get() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        let user = User {
            id: UserId::from_u128(0x1111),
            sub: "theuser".to_string(),
            display_name: None,
            is_superuser: true,
        };

        {
            let user = user.clone();
            service
                .expect_handle_user_get()
                .times(1)
                .return_once(|_, _| {
                    Ok(types::user::get::ResponseBody(
                        pib_service_core_types::DataPage {
                            data: vec![user],
                            pagination: pib_service_core_types::DataPagePagination {
                                total_elements: 11,
                                elements_per_page: 12,
                                current_page: 13,
                                total_pages: 14,
                            },
                        },
                    ))
                });
        }

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/user")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_success()
            .json::<types::user::get::ResponseBody>();

        assert_eq!(
            response,
            types::user::get::ResponseBody(pib_service_core_types::DataPage {
                data: vec![user],
                pagination: pib_service_core_types::DataPagePagination {
                    total_elements: 11,
                    elements_per_page: 12,
                    current_page: 13,
                    total_pages: 14,
                }
            })
        )
    }

    #[tokio::test]
    async fn success_body_get() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        service
            .expect_handle_body_get()
            .times(1)
            .return_once(|_, _| {
                Ok(types::body::get::ResponseBody(
                    pib_service_core_types::DataPage::empty(15.try_into().unwrap()),
                ))
            });

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/body")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_success()
            .json::<types::body::get::ResponseBody>();

        assert_eq!(
            response,
            types::body::get::ResponseBody(pib_service_core_types::DataPage::empty(
                15.try_into().unwrap()
            ))
        )
    }

    #[tokio::test]
    async fn success_body_post() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        service
            .expect_handle_body_post()
            .times(1)
            .return_once(|_, _| Ok(BodyId::from_u128(0x94949494)));

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        server
            .post("/body")
            .json(&types::body::post::RequestBody {
                short_name: None,
                name: "Hintertupfing".parse().unwrap(),
                website: None,
                license: None,
                license_valid_since: None,
                oparl_since: None,
                ags: "12345".to_string(),
                rgs: None,
                contact_email: None,
                contact_name: None,
                classification: None,
                is_public: true,
            })
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_see_other()
            .assert_header("Location", "/body/00000000-0000-0000-0000-000094949494");
    }

    #[tokio::test]
    async fn success_body_by_id_get() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        let response_body = types::body::by_id::get::ResponseBody(types::body::Body {
            id: BodyId::from_u128(0x17593122_2260_450e_a869_fe160e69ceb2),
            created: "2026-04-07T05:06:07+01:00".parse().unwrap(),
            modified: "2026-04-08T05:06:07+01:00".parse().unwrap(),
            short_name: None,
            name: "Hintertupfing".parse().unwrap(),
            website: None,
            license: None,
            license_valid_since: None,
            oparl_since: None,
            ags: "12345".to_string(),
            rgs: None,
            contact_email: None,
            contact_name: None,
            classification: None,
            is_public: true,
        });
        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        {
            let response_body = response_body.clone();
            service
                .expect_handle_body_by_id_get()
                .times(1)
                .return_once(|_, _| Ok(response_body));
        }

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/body/17593122-2260-450e-a869-fe160e69ceb2")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_success()
            .json::<types::body::by_id::get::ResponseBody>();

        assert_eq!(response, response_body);
    }

    #[tokio::test]
    async fn success_body_by_id_meeting_get() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        service
            .expect_handle_body_by_id_meeting_get()
            .times(1)
            .return_once(|_, _, _| {
                Ok(types::body::by_id::meeting::get::ResponseBody(
                    pib_service_core_types::DataPage {
                        data: vec![],
                        pagination: pib_service_core_types::DataPagePagination {
                            total_elements: 11,
                            current_page: 12,
                            elements_per_page: 13,
                            total_pages: 14,
                        },
                    },
                ))
            });

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/body/152cf529-df96-45ba-833a-54509a26d064/meeting")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_success()
            .json::<types::body::by_id::meeting::get::ResponseBody>();

        assert_eq!(
            response,
            types::body::by_id::meeting::get::ResponseBody(pib_service_core_types::DataPage {
                data: vec![],
                pagination: pib_service_core_types::DataPagePagination {
                    total_elements: 11,
                    current_page: 12,
                    elements_per_page: 13,
                    total_pages: 14,
                }
            })
        )
    }

    #[tokio::test]
    async fn success_body_by_id_meeting_post() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        service
            .expect_handle_body_by_id_meeting_post()
            .times(1)
            .return_once(|_, _, _| Ok(MeetingId::from_u128(0x131313)));

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        server
            .post("/body/152cf529-df96-45ba-833a-54509a26d064/meeting")
            .json(&types::body::by_id::meeting::post::RequestBody {
                organization_id: OrganizationId::from_u128(0x999999999),
                name: Some("Hintertupfing".parse().unwrap()),
                license: None,
                meeting_state: "planned".to_string(),
                keyword: None,
                start: None,
                end: None,
                web: None,
            })
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_see_other()
            .assert_header(
                "Location",
                "/body/152cf529-df96-45ba-833a-54509a26d064/meeting/00000000-0000-0000-0000-000000131313",
            );
    }

    #[tokio::test]
    async fn success_body_by_id_meeting_by_id_get() {
        let mut api_auth = MockApiAuth::new();
        let mut service = MockService::<pib_service_facade::Error>::new();

        let response_body = types::body::by_id::meeting::by_id::get::ResponseBody(
            types::body::by_id::meeting::Meeting {
                id: MeetingId::from_u128(0xcf4cb545_4e10_4205_8cb4_7ddcbba0e0dd),
                body_id: BodyId::from_u128(0x17593122_2260_450e_a869_fe160e69ceb2),
                organization_id: OrganizationId::from_u128(0xb991c4e7_1283_4997_b042_f962d19895cf),
                created: "2026-04-07T05:06:07+01:00".parse().unwrap(),
                modified: "2026-04-08T05:06:07+01:00".parse().unwrap(),
                name: None,
                meeting_state: None,
                start: None,
                end: None,
                license: None,
                web: None,
            },
        );
        api_auth
            .expect_authorize()
            .times(1)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .return_once(|_| Ok(Some(example_user_info())));

        {
            let response_body = response_body.clone();
            service
                .expect_handle_body_by_id_meeting_by_id_get()
                .times(1)
                .return_once(|_, _, _| Ok(response_body));
        }

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        let response = server
            .get("/body/17593122-2260-450e-a869-fe160e69ceb2/meeting/cf4cb545-4e10-4205-8cb4-7ddcbba0e0dd")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_success()
            .json::<types::body::by_id::meeting::by_id::get::ResponseBody>();

        assert_eq!(response, response_body);
    }

    #[tokio::test]
    async fn forbid() {
        let mut api_auth = MockApiAuth::new();
        let service = MockService::<pib_service_facade::Error>::new();

        api_auth
            .expect_authorize()
            .times(1..)
            .with(eq(http::HeaderValue::from_static("xxyyzz")))
            .returning(|_| Ok(None));

        let app = router(Auth::new(Arc::new(api_auth)), Arc::new(service)).await;

        let server = TestServer::new(app);

        server
            .get("/me/profile")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .patch("/me/profile")
            .json(&types::me::profile::patch::RequestBody {
                display_name: Some(Some("hello".to_string())),
            })
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .get("/body")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .post("/body")
            .json(&types::body::post::RequestBody {
                short_name: None,
                name: "hello".parse().unwrap(),
                website: None,
                license: None,
                license_valid_since: None,
                oparl_since: None,
                ags: "12345".to_string(),
                rgs: None,
                contact_email: None,
                contact_name: None,
                classification: None,
                is_public: true,
            })
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .get("/body/190c9a32-6993-462c-afcb-9f11ad7e1f33")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .get("/body/190c9a32-6993-462c-afcb-9f11ad7e1f33/meeting")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .post("/body/190c9a32-6993-462c-afcb-9f11ad7e1f33/meeting")
            .json(&types::body::by_id::meeting::post::RequestBody {
                organization_id: OrganizationId::from_u128(0x114488),
                name: None,
                license: None,
                meeting_state: "planned".to_string(),
                keyword: None,
                start: None,
                end: None,
                web: None,
            })
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();

        server
            .get("/body/190c9a32-6993-462c-afcb-9f11ad7e1f33/meeting/0cec81a2-d5d9-4c61-a970-e752bbca7783")
            .add_header("Authorization", "xxyyzz")
            .await
            .assert_status_unauthorized();
    }
}
