// Copyright (C) 2021  Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::{web, HttpResponse, Responder};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};

use crate::errors::*;
use crate::AppData;

#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
pub struct BuildDetails {
    pub version: &'static str,
    pub git_commit_hash: &'static str,
}

pub mod routes {
    use serde::{Deserialize, Serialize};

    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
    pub struct Stats {
        pub percentile_benches: &'static str,
    }

    impl Stats {
        pub const fn new() -> Self {
            Self {
                percentile_benches: "/api/v1/stats/benches/percentile",
            }
        }
    }
}

/// Get difficulty factor with max time limit for percentile of stats
#[actix_web_codegen_const_routes::post(
    path = "crate::V1_API_ROUTES.stats.percentile_benches"
)]
async fn percentile_benches(
    data: AppData,
    payload: web::Json<PercentileReq>,
) -> ServiceResult<impl Responder> {
    struct Count {
        count: Option<i64>,
    }

    let count = sqlx::query_as!(
        Count,
        "SELECT COUNT(difficulty) FROM survey_benches WHERE duration <= $1;",
        payload.time as f32
    )
    .fetch_one(&data.db)
    .await?;

    if count.count.is_none() {
        return Ok(HttpResponse::Ok().json(PercentileResp {
            difficulty_factor: None,
        }));
    }

    let count = count.count.unwrap();

    if count < 2 {
        return Ok(HttpResponse::Ok().json(PercentileResp {
            difficulty_factor: None,
        }));
    }

    let location = ((count - 1) as f64 * (payload.percentile / 100.00)) + 1.00;
    let fraction = location - location.floor();

    async fn get_data_at_location(
        data: &crate::Data,
        time: u32,
        location: i64,
    ) -> ServiceResult<Option<u32>> {
        struct Difficulty {
            difficulty: Option<i32>,
        }

        match sqlx::query_as!(
            Difficulty,
            "SELECT
            difficulty
        FROM
            survey_benches
        WHERE
            duration <= $1
        ORDER BY difficulty ASC LIMIT 1 OFFSET $2;",
            time as f32,
            location as i64 - 1,
        )
        .fetch_one(&data.db)
        .await
        {
            Ok(res) => Ok(Some(res.difficulty.unwrap() as u32)),
            Err(sqlx::Error::RowNotFound) => Ok(None),
            Err(e) => Err(e.into()),
        }
    }

    if fraction > 0.00 {
        if let (Some(base), Some(ceiling)) = (
            get_data_at_location(&data, payload.time, location.floor() as i64).await?,
            get_data_at_location(&data, payload.time, location.floor() as i64 + 1)
                .await?,
        ) {
            let res = base as u32 + ((ceiling - base) as f64 * fraction).floor() as u32;

            return Ok(HttpResponse::Ok().json(PercentileResp {
                difficulty_factor: Some(res),
            }));
        }
    } else {
        if let Some(base) =
            get_data_at_location(&data, payload.time, location.floor() as i64).await?
        {
            let res = base as u32;

            return Ok(HttpResponse::Ok().json(PercentileResp {
                difficulty_factor: Some(res),
            }));
        }
    };
    Ok(HttpResponse::Ok().json(PercentileResp {
        difficulty_factor: None,
    }))
}

#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
/// Health check return datatype
pub struct PercentileReq {
    time: u32,
    percentile: f64,
}

#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
/// Health check return datatype
pub struct PercentileResp {
    difficulty_factor: Option<u32>,
}

pub fn services(cfg: &mut web::ServiceConfig) {
    cfg.service(percentile_benches);
}

#[cfg(test)]
mod tests {
    use actix_web::{http::StatusCode, test, App};

    use super::*;
    use crate::api::v1::services;
    use crate::tests::get_test_data;
    use crate::*;

    #[actix_rt::test]
    async fn stats_bench_work() {
        use crate::tests::*;

        const NAME: &str = "benchstatsuesr";
        const EMAIL: &str = "benchstatsuesr@testadminuser.com";
        const PASSWORD: &str = "longpassword2";

        const DEVICE_USER_PROVIDED: &str = "foo";
        const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
        const THREADS: i32 = 4;

        let data = get_test_data().await;
        {
            delete_user(NAME, &data).await;
        }

        let (creds, signin_resp) =
            register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
        let cookies = get_cookie!(signin_resp);
        let app = get_app!(data).await;

        let survey = get_survey_user(data.clone()).await;
        let survey_cookie = get_cookie!(survey);
        let campaign = create_new_campaign(NAME, data.clone(), cookies.clone()).await;
        let campaign_config =
            get_campaign_config(&campaign, data.clone(), survey_cookie.clone()).await;

        assert_eq!(DIFFICULTIES.to_vec(), campaign_config.difficulties);

        let submit_payload = crate::api::v1::bench::Submission {
            device_user_provided: DEVICE_USER_PROVIDED.into(),
            device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
            threads: THREADS,
            benches: BENCHES.clone(),
            submission_type: crate::api::v1::bench::SubmissionType::Wasm,
        };

        submit_bench(&submit_payload, &campaign, survey_cookie, data.clone()).await;

        let msg = PercentileReq {
            time: 1,
            percentile: 99.00,
        };
        let resp = test::call_service(
            &app,
            post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
        )
        .await;
        assert_eq!(resp.status(), StatusCode::OK);
        let resp: PercentileResp = test::read_body_json(resp).await;

        assert!(resp.difficulty_factor.is_none());

        let msg = PercentileReq {
            time: 1,
            percentile: 100.00,
        };

        let resp = test::call_service(
            &app,
            post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
        )
        .await;
        assert_eq!(resp.status(), StatusCode::OK);
        let resp: PercentileResp = test::read_body_json(resp).await;

        assert!(resp.difficulty_factor.is_none());

        let msg = PercentileReq {
            time: 2,
            percentile: 100.00,
        };

        let resp = test::call_service(
            &app,
            post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
        )
        .await;
        assert_eq!(resp.status(), StatusCode::OK);
        let resp: PercentileResp = test::read_body_json(resp).await;

        assert_eq!(resp.difficulty_factor.unwrap(), 2);

        let msg = PercentileReq {
            time: 5,
            percentile: 90.00,
        };

        let resp = test::call_service(
            &app,
            post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
        )
        .await;
        assert_eq!(resp.status(), StatusCode::OK);
        let resp: PercentileResp = test::read_body_json(resp).await;

        assert_eq!(resp.difficulty_factor.unwrap(), 4);
        delete_user(NAME, &data).await;
    }
}
