use std::collections::HashMap;

use chrono::Utc;
use laboratory::{describe, Suite};
use reqwest::{Client, Method, StatusCode};
use serde::Deserialize;

use sylvia_iot_auth::models::Model as AuthModel;
use sylvia_iot_broker::models::Model as BrokerModel;

use super::{
    libs::{
        create_application, create_client, create_device, create_device_route,
        create_dldata_buffer, create_network, create_network_route, create_unit, create_user,
        create_users_tokens, new_state, TOKEN_MANAGER,
    },
    stop_auth_broker_svc,
};
use crate::{libs::mq::emqx, TestState, TEST_BROKER_BASE};

mod application;
mod auth;
mod client;
mod device;
mod device_route;
mod dldata_buffer;
mod network;
mod network_route;
mod unit;
mod user;

#[derive(Deserialize)]
struct Stats {
    #[serde(rename = "consumers")]
    _consumers: usize,
    messages: usize,
    #[serde(rename = "publishRate")]
    publish_rate: f64,
    #[serde(rename = "deliverRate")]
    _deliver_rate: f64,
}

pub const STATE: &'static str = "routes/v1";

pub fn suite(mqtt_engine: &'static str) -> Suite<TestState> {
    describe(format!("routes.v1 - {}", mqtt_engine), move |context| {
        context.describe("pure bridge", |context| {
            context.describe("auth", |context| {
                context.it("GET /tokeninfo", auth::tokeninfo);
                context.it("POST /logout", auth::logout);
            });

            context.describe("user", |context| {
                context.it("GET /user", user::get);
                context.it("PATCH /user", user::patch);
                context.it("POST /user", user::post_admin);
                context.it("GET /user/count", user::get_admin_count);
                context.it("GET /user/list", user::get_admin_list);
                context.it("GET /user/{userId}", user::get_admin);
                context.it("PATCH /user/{userId}", user::patch_admin);
                context.it("DELETE /user/{userId}", user::delete_admin);
            });

            context.describe("client", |context| {
                context.it("POST /client", client::post);
                context.it("GET /client/count", client::get_count);
                context.it("GET /client/list", client::get_list);
                context.it("GET /client/{clientId}", client::get);
                context.it("PATCH /client/{clientId}", client::patch);
                context.it("DELETE /client/{clientId}", client::delete);
                context.it("DELETE /client/user/{userId}", client::delete_user);
            });

            context.describe("application", |context| {
                context.it("GET /application/count", application::get_count);
                context.it("GET /application/list", application::get_list);
            });

            context.describe("device", |context| {
                context.it("POST /device", device::post);
                context.it("POST /device/bulk", device::post_bulk);
                context.it("POST /device/bulk-delete", device::post_bulk_del);
                context.it("POST /device/range", device::post_range);
                context.it("POST /device/range-delete", device::post_range_del);
                context.it("GET /device/count", device::get_count);
                context.it("GET /device/list", device::get_list);
                context.it("GET /device/{deviceId}", device::get);
                context.it("PATCH /device/{deviceId}", device::patch);
                context.it("DELETE /device/{deviceId}", device::delete);
            });

            context.describe("device_route", |context| {
                context.it("POST /device-route", device_route::post);
                context.it("POST /device-route/bulk", device_route::post_bulk);
                context.it(
                    "POST /device-route/bulk-delete",
                    device_route::post_bulk_del,
                );
                context.it("POST /device-route/range", device_route::post_range);
                context.it(
                    "POST /device-route/range-delete",
                    device_route::post_range_del,
                );
                context.it("GET /device-route/count", device_route::get_count);
                context.it("GET /device-route/list", device_route::get_list);
                context.it("GET /device-route/list", device_route::get_list);
                context.it("DELETE /device-route/{routeId}", device_route::delete);
            });

            context.describe("dldata_buffer", |context| {
                context.it("GET /dldata-buffer/count", dldata_buffer::get_count);
                context.it("GET /dldata-buffer/list", dldata_buffer::get_list);
                context.it("DELETE /dldata-buffer/{routeId}", dldata_buffer::delete);
            });

            context.describe("network", |context| {
                context.it("GET /network/count", network::get_count);
                context.it("GET /network/list", network::get_list);
            });

            context.describe("network_route", |context| {
                context.it("POST /network-route", network_route::post);
                context.it("GET /network-route/count", network_route::get_count);
                context.it("GET /network-route/list", network_route::get_list);
                context.it("DELETE /network-route/{routeId}", network_route::delete);
            });

            context.describe("unit", |context| {
                context.it("POST /unit", unit::post);
                context.it("GET /unit/count", unit::get_count);
                context.it("GET /unit/list", unit::get_list);
                context.it("GET /unit/{unitId}", unit::get);
                context.it("PATCH /unit/{unitId}", unit::patch);
            });

            context.before_all(move |state| {
                create_list_rsc(state);
            });
        });

        context.describe("application", |context| {
            context.it("POST /application", application::post);
            context.it(
                "POST /application with invalid parameters",
                application::post_invalid,
            );
            context.it("GET /application/{applicationId}", application::get);
            context.it(
                "GET /application/{applicationId} with invalid parameters",
                application::get_invalid,
            );
            context.it("PATCH /application/{applicationId}", application::patch);
            context.it(
                "PATCH /application/{applicationId} with invalid parameters",
                application::patch_invalid,
            );
            context.it("DELETE /application/{applicationId}", application::delete);
            context.it(
                "DELETE /application/{applicationId} with invalid parameters",
                application::delete_invalid,
            );
            context.it("GET /application/{applicationId}/stats", application::stats);
            context.it(
                "GET /application/{applicationId}/stats with invalid parameters",
                application::stats_invalid,
            );
            context.it(
                "POST /application/{applicationId}/dldata",
                application::dldata,
            );
            context.it(
                "POST /application/{applicationId}/dldata with invalid parameters",
                application::dldata_invalid,
            );

            context.after_each(application::after_each_fn);
        });

        context.describe("network", |context| {
            context.it("POST /network", network::post);
            context.it(
                "POST /network with invalid parameters",
                network::post_invalid,
            );
            context.it("GET /network/{networkId}", network::get);
            context.it(
                "GET /network/{networkId} with invalid parameters",
                network::get_invalid,
            );
            context.it("PATCH /network/{networkId}", network::patch);
            context.it(
                "PATCH /network/{networkId} with invalid parameters",
                network::patch_invalid,
            );
            context.it("DELETE /network/{networkId}", network::delete);
            context.it(
                "DELETE /network/{networkId} with invalid parameters",
                network::delete_invalid,
            );
            context.it("GET /network/{networkId}/stats", network::stats);
            context.it(
                "GET /network/{networkId}/stats with invalid parameters",
                network::stats_invalid,
            );
            context.it("POST /network/{networkId}/uldata", network::uldata);
            context.it(
                "POST /network/{networkId}/uldata with invalid parameters",
                network::uldata_invalid,
            );

            context.after_each(network::after_each_fn);
        });

        context.describe("unit", |context| {
            context.it("DELETE /unit/{unitId}", unit::delete);
            context.it(
                "DELETE /unit/{unitId} with invalid parameters",
                unit::delete_invalid,
            );

            context.before_all(move |state| {
                unit::create_delete_rsc(state);
            });
        });

        context
            .before_all(move |state| {
                state.insert(STATE, new_state(Some(mqtt_engine), None));

                let state = state.get(STATE).unwrap();
                create_users_tokens(state);
            })
            .after_all(after_all_fn);
    })
}

fn after_all_fn(state: &mut HashMap<&'static str, TestState>) -> () {
    let state = state.get_mut(STATE).unwrap();
    let _ = state.rumqttd_handles.take();
    let runtime = state.runtime.as_ref().unwrap();

    if let Err(e) = runtime.block_on(async { emqx::after_del_api_key().await }) {
        println!("delete EMQX API key error: {}", e);
    }

    stop_auth_broker_svc(state);
}

fn create_list_rsc(state: &mut HashMap<&'static str, TestState>) -> () {
    let state = state.get(STATE).unwrap();
    let runtime = state.runtime.as_ref().unwrap();
    let auth_db = state.auth_db.as_ref().unwrap();
    let broker_db = state.broker_db.as_ref().unwrap();

    let now = Utc::now();
    let role = HashMap::<String, bool>::new();
    let user_id = "manager";

    for i in 100..201 {
        let name = format!("list{}", i);
        let name = name.as_str();
        let item = create_user(name, now, role.clone());
        if let Err(e) = runtime.block_on(async { auth_db.user().add(&item).await }) {
            panic!("create list user error: {}", e);
        }
        let item = create_client(name, user_id, None);
        if let Err(e) = runtime.block_on(async { auth_db.client().add(&item).await }) {
            panic!("create list client error: {}", e);
        }
        let item = create_unit(name, user_id);
        if let Err(e) = runtime.block_on(async { broker_db.unit().add(&item).await }) {
            panic!("create list unit error: {}", e);
        }
        let item = create_application(name, "mqtt://localhost", name);
        if let Err(e) = runtime.block_on(async { broker_db.application().add(&item).await }) {
            panic!("create list application error: {}", e);
        }
        let item = create_network(name, "mqtt://localhost", name);
        if let Err(e) = runtime.block_on(async { broker_db.network().add(&item).await }) {
            panic!("create list network error: {}", e);
        }
        let item = create_device(name, name, name, false);
        if let Err(e) = runtime.block_on(async { broker_db.device().add(&item).await }) {
            panic!("create list device error: {}", e);
        }
        let item = create_device_route(name, name, name, name, name);
        if let Err(e) = runtime.block_on(async { broker_db.device_route().add(&item).await }) {
            panic!("create list device route error: {}", e);
        }
        let item = create_network_route(name, name, name, name);
        if let Err(e) = runtime.block_on(async { broker_db.network_route().add(&item).await }) {
            panic!("create list network route error: {}", e);
        }
        let item = create_dldata_buffer(name, name, name, name, name);
        if let Err(e) = runtime.block_on(async { broker_db.dldata_buffer().add(&item).await }) {
            panic!("create list dldata buffer error: {}", e);
        }
    }
}

async fn remove_unit(client: &Client, unit_id: &str) -> Result<(), String> {
    let uri = format!("{}/api/v1/unit/{}", TEST_BROKER_BASE, unit_id);
    let req = match client
        .request(Method::DELETE, uri.as_str())
        .header(
            reqwest::header::AUTHORIZATION,
            format!("Bearer {}", TOKEN_MANAGER),
        )
        .build()
    {
        Err(e) => return Err(format!("generate request error: {}", e)),
        Ok(req) => req,
    };
    match client.execute(req).await {
        Err(e) => return Err(format!("execute request error: {}", e)),
        Ok(resp) => match resp.status() {
            StatusCode::NO_CONTENT => Ok(()),
            _ => Err(format!("execute request with status {}", resp.status())),
        },
    }
}

async fn remove_network(client: &Client, network_id: &str) -> Result<(), String> {
    let uri = format!("{}/api/v1/network/{}", TEST_BROKER_BASE, network_id);
    let req = match client
        .request(Method::DELETE, uri.as_str())
        .header(
            reqwest::header::AUTHORIZATION,
            format!("Bearer {}", TOKEN_MANAGER),
        )
        .build()
    {
        Err(e) => return Err(format!("generate request error: {}", e)),
        Ok(req) => req,
    };
    match client.execute(req).await {
        Err(e) => return Err(format!("execute request error: {}", e)),
        Ok(resp) => match resp.status() {
            StatusCode::NO_CONTENT => Ok(()),
            _ => Err(format!("execute request with status {}", resp.status())),
        },
    }
}
