use crate::records::HealthRecord;
use crate::records::StepsRecord;
use chrono::{SecondsFormat};
use rand::prelude::*;
use rand::rngs::SmallRng;
use rand::SeedableRng;
use serde::de;
use serde::{Deserialize, Serialize};
use std::ffi::CStr;
use std::mem;
use std::os::raw::{c_char, c_void};

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Platform {
    IOS,
    Android,
    JS,
    Unknown,
}

#[link(wasm_import_module = "hubro_sdk")]
unsafe extern "C" {
    fn get_health_connect_records(record_type: i32, from: i32, to: i32) -> *mut c_char;
    fn get_health_connect_number_of_records(record_type: i32, from: i32, to: i32) -> i32;
    fn print_line(nf_name: *mut c_char);
    fn get_platform() -> *mut c_char;
}

pub extern "C" fn allocate(size: usize) -> *mut c_void {
    let mut buf = Vec::with_capacity(size);
    let ptr = buf.as_mut_ptr();
    mem::forget(buf);
    return ptr as *mut c_void;
}

pub extern "C" fn dealloc(ptr: *mut c_void, cap: usize) {
    unsafe {
        let _buf = Vec::from_raw_parts(ptr, 0, cap);
    }
}

fn fetch_records_count(record_type: i32, from: i32, to: i32) -> i32 {
    let mut s;
    loop {
        s = unsafe { get_health_connect_number_of_records(record_type, from, to) };
        if s != -1 {
            break;
        }
    }
    s
}

fn fetch_records<T: de::DeserializeOwned + HealthRecord>(
    record_type: i32,
    from: i32,
    to: i32,
) -> Vec<T> {
    if fetch_records_count(record_type, from, to) > 0 {
        let s = unsafe { get_health_connect_records(record_type, from, to) };
        let json_data = unsafe { CStr::from_ptr(s).to_bytes().to_vec() };
        let subject_str = std::str::from_utf8(&json_data).unwrap();
        return serde_json::from_str::<Vec<T>>(subject_str).unwrap();
    } else {
        Vec::<T>::new()
    }
}

// pub extern "C" fn get_hc_steps_records_count(from: i32, to: i32) -> i32 {
//     fetch_records_count(StepsRecord::IDENTIFIER, from, to)
// }

pub extern "C" fn get_health_records<
    T: de::DeserializeOwned + HealthRecord,
>(
    from: i32,
    to: i32,
) -> Vec<T> {
    fetch_records::<T>(T::IDENTIFIER, from, to)
}
pub extern "C" fn generate_sample_steps_records(from: i32, to: i32) -> Vec<StepsRecord> {
    let mut seed = [0u8; 16];
    getrandom::getrandom(&mut seed).unwrap_or_else(|_| {
        let b = (from as u64).to_le_bytes();
        seed[0..8].copy_from_slice(&b);
        seed[8..16].copy_from_slice(&b);
    });
    let mut rng = SmallRng::from_seed(seed);

    let mut records = Vec::new();
    let start_time = chrono::DateTime::from_timestamp(from as i64, 0).unwrap().naive_utc();
    let end_time = chrono::DateTime::from_timestamp(to as i64, 0).unwrap().naive_utc();
    let duration = end_time.signed_duration_since(start_time);
    let days = duration.num_days();

    for day in 0..days {
        let base_timestamp = start_time + chrono::Duration::days(day);
        let num_records = rng.gen_range(3..8);

        for hour in 0..num_records {
            let timestamp = base_timestamp + chrono::Duration::hours(hour * 4);
            let steps = rng.gen_range(100..5000);
            let record = StepsRecord {
                count: steps,
                startTime: chrono::DateTime::from_timestamp(
                    timestamp.and_utc().timestamp(),
                    timestamp.and_utc().timestamp_subsec_nanos(),
                )
                .unwrap()
                .to_rfc3339_opts(SecondsFormat::Millis, true),
                endTime: chrono::DateTime::from_timestamp(
                    (timestamp + chrono::Duration::hours(4)).and_utc().timestamp(),
                    (timestamp + chrono::Duration::hours(4))
                        .and_utc()
                        .timestamp_subsec_nanos(),
                )
                .unwrap()
                .to_rfc3339_opts(SecondsFormat::Millis, true),
            };
            records.push(record);
        }
    }
    records
}

pub extern "C" fn debug_print_line(output: &str) {
    let size = output.len();
    let ptr = allocate(size + 1) as *mut c_char;
    unsafe {
        std::ptr::copy(output.as_ptr(), ptr as *mut u8, size);
        *ptr.add(size) = 0;
        print_line(ptr);
    }
}

pub fn get_current_platform() -> Platform {
    let ptr = unsafe { get_platform() };
    if ptr.is_null() {
        return Platform::Unknown;
    }
    let s = unsafe { CStr::from_ptr(ptr).to_string_lossy().to_string() };
    match s.to_lowercase().as_str() {
        "ios" => Platform::IOS,
        "android" => Platform::Android,
        "js" => Platform::JS,
        _ => Platform::Unknown,
    }
}
