use chrono::{DateTime, NaiveDateTime, Utc};
use chrono::SecondsFormat::Millis;
use fhirbolt::model::r4b::resources::{Bundle, BundleEntry, ObservationEffective};
use fhirbolt::model::r4b::{Resource, types};
use fhirbolt::model::r4b::types::{Code, Coding, Period, Uri};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use spin_sdk::http::Method::{Post};

pub struct Types {}

impl Types {
    pub const RECORD_TYPE_SMBG: &'static str = "14745-4";
    pub const RECORD_TYPE_STEPS: &'static str = "55423-8";
    pub const RECORD_TYPE_HDL: &'static str = "2085-9";
    pub const RECORD_TYPE_LDL: &'static str = "2089-1";
}

pub struct Client {
    pub base_url: Option<String>,
}

#[derive(Serialize, Deserialize)]
struct AnonymizedUser {
    fhir_user: Option<String>,
    fhir_token: Option<String>
}

impl Client {
    const HUBRO_SERVICE_URL: &'static str = "http://hubro-api.hubro.svc.cluster.local";

    pub fn generate_bundle() -> anyhow::Result<Bundle> {
        Ok(Bundle {
            entry: vec![],
            r#type: types::Code {
                value: Some("Batch".into()),
                ..Default::default()
            },
            ..Default::default()
        })
    }

    pub fn generate_bundle_entry(record_type: &str, display: &str, start_time: &str, end_time: Option<&str>, unit: &str, value: &str, user_id: &str) -> anyhow::Result<BundleEntry> {
        let o = fhirbolt::model::r4b::resources::Observation {
            status: "final".into(),
            code: Box::new(types::CodeableConcept {
                coding: Vec::from([Coding {
                    code: Some(record_type.into()),
                    display: Some(display.into()),
                    system: Some("http://loinc.org".into()),
                    ..Default::default()
                }]),
                ..Default::default()
            }),
            subject: Some(Box::new(types::Reference {
                reference: Some(types::String::from(format!("Patient/{}", user_id).to_string())),
                ..Default::default()
            })),
            performer: Vec::from([types::Reference {
                reference: None,
                ..Default::default()
            }]),
            effective: match end_time {
                None => {
                    Some(ObservationEffective::DateTime(fhirbolt::model::r4b::types::DateTime {
                        value: Some(start_time.into()),
                        ..Default::default()
                    }))
                }
                Some(_) => {
                    Some(ObservationEffective::Period(Box::new(Period {
                        start: Some(fhirbolt::model::r4b::types::DateTime {
                            value: Some(start_time.into()),
                            ..Default::default()
                        }),
                        end: Some(fhirbolt::model::r4b::types::DateTime {
                            value: Some(end_time.unwrap().into()),
                            ..Default::default()
                        }),
                        ..Default::default()
                    })))
                }
            },
            value: Some(fhirbolt::model::r4b::resources::ObservationValue::Quantity(Box::new(fhirbolt::model::r4b::types::Quantity {
                system: Some(Uri {
                    value: Some("http://unitsofmeasure.org".into()),
                    ..Default::default()
                }),
                code: Some(Code {
                    value: Some(unit.into()),
                    ..Default::default()
                }),
                value: Some(fhirbolt::model::r4b::types::Decimal {
                    value: Some(value.into()),
                    ..Default::default()
                }),
                ..Default::default()
            }))),
            ..Default::default()
        };
        let be = BundleEntry {
            id: None,
            extension: vec![],
            modifier_extension: vec![],
            link: vec![],
            full_url: None,
            resource: Some(Resource::Observation(Box::new(o))),
            search: None,
            request: None,
            response: None,
        };
        Ok(be)
    }

    pub fn get_target_url(&self) -> Result<String, anyhow::Error> {
        match self.base_url.to_owned() {
            None => {
                Ok(Client::HUBRO_SERVICE_URL.to_string())
            }
            Some(val) => {
                Ok(val)
            }
        }
    }

    pub async fn send_bundle(&self, bearer: &str, body: Option<Value>) -> Result<(), anyhow::Error> {
        let target_url = self.get_target_url()?;

        let res: spin_sdk::http::Response = spin_sdk::http::send(
            spin_sdk::http::Request::builder()
                .method(spin_sdk::http::Method::Post)
                .header("Accept", "application/json")
                .header("Authorization", format!("Bearer {bearer}"))
                .header("Content-type", "application/json")
                .uri(format!("{target_url}/plugins/post_data"))
                .body(Some(body.unwrap().to_string())).build()
        ).await?;

        Ok(())
    }

    pub async fn anonymize_user(&self, user: &str, study_id: &str) -> Result<(String, String), anyhow::Error> {
        let target_url = self.get_target_url()?;
        let data = json!({
          "user": user,
          "study": study_id
        });
        let res: spin_sdk::http::Response = spin_sdk::http::send(
            spin_sdk::http::Request::builder()
                .method(spin_sdk::http::Method::Post)
                .header("Accept", "application/json")
                .header("Content-type", "application/json")
                .uri(format!("{target_url}/plugins/anonymize"))
                .body(Some(data.to_string())).build()
        ).await?;
        let body = std::str::from_utf8(res.body())?;
        let data: AnonymizedUser = serde_json::from_str(body)?;
        Ok((data.fhir_user.unwrap(), data.fhir_token.unwrap()))
    }
}