use crate::dtob_ffi;
use crate::decode_dtob_bytes;
use std::fs;
use std::path::Path;
use chrono::{TimeZone, Utc};

struct TableEntry {
    xvuid: String,
    title: String,
    cid: String,
    ccid: String,
    created_ts: i64,
    edited_ts: i64,
}

pub fn generate_table_to_buf(maps_path: &Path, out: &mut Vec<u8>) {
    let map_path = maps_path.join("xvuid_to_meta.dtob");

    let mut entries = Vec::new();

    if let Ok(data) = fs::read(&map_path) {
        unsafe {
            if let Some(map_root) = decode_dtob_bytes(&data) {
                let len = dtob_ffi::ffi_dtob_kvset_len(map_root);
                for i in 0..len {
                    let key_ptr = dtob_ffi::ffi_dtob_kvset_key(map_root, i);
                    let val_ptr = dtob_ffi::ffi_dtob_kvset_value_at(map_root, i);
                    
                    if !key_ptr.is_null() && !val_ptr.is_null() {
                        let key_c = std::ffi::CStr::from_ptr(key_ptr);
                        let xvuid_str = key_c.to_string_lossy().into_owned();
                        
                        let v_title = dtob_ffi::ffi_dtob_array_get(val_ptr, 0);
                        let v_cid = dtob_ffi::ffi_dtob_array_get(val_ptr, 1);
                        let v_ccid = dtob_ffi::ffi_dtob_array_get(val_ptr, 2);
                        let v_created = dtob_ffi::ffi_dtob_array_get(val_ptr, 3);
                        let v_edited = dtob_ffi::ffi_dtob_array_get(val_ptr, 4);
                        
                        let title_str = get_raw_str(v_title);
                        let cid_str = hex::encode(get_raw_bytes(v_cid));
                        let ccid_str = hex::encode(get_raw_bytes(v_ccid));
                        
                        let created_ts = dtob_ffi::ffi_dtob_get_u64(v_created as *mut _) as i64;
                        let edited_ts = dtob_ffi::ffi_dtob_get_u64(v_edited as *mut _) as i64;
                        
                        entries.push(TableEntry {
                            xvuid: xvuid_str,
                            title: title_str,
                            cid: cid_str,
                            ccid: ccid_str,
                            created_ts,
                            edited_ts,
                        });
                    }
                }
                dtob_ffi::ffi_dtob_free(map_root as *mut _);
            }
        }
    } else {
        eprintln!("No parsed metadata map found. Have you run 'sculblog cache-maps'?");
    }

    entries.sort_by(|a, b| b.edited_ts.cmp(&a.edited_ts));
    encode_entries_to_buf(&entries, out);
}

pub fn generate_table(maps_path: &Path) {
    let mut buf = Vec::new();
    generate_table_to_buf(maps_path, &mut buf);
    use std::io::Write;
    let _ = std::io::stdout().write_all(&buf);
    let _ = std::io::stdout().flush();
}

pub fn generate_table_for_xvuid_to_buf(_maps_path: &Path, cache: &crate::maps::MapsCache, xvuid: &str, out: &mut Vec<u8>) {
    let meta_root = match cache.meta_root() {
        Some(r) => r,
        None => { eprintln!("meta map missing"); return; }
    };
    unsafe {
        let key_c = std::ffi::CString::new(xvuid).unwrap();
        let val_ptr = dtob_ffi::ffi_kvset_get(meta_root, key_c.as_ptr());
        
        if val_ptr.is_null() {
            eprintln!("No metadata found for {}", xvuid);
            return;
        }

        let v_title = dtob_ffi::ffi_dtob_array_get(val_ptr, 0);
        let v_cid = dtob_ffi::ffi_dtob_array_get(val_ptr, 1);
        let v_ccid = dtob_ffi::ffi_dtob_array_get(val_ptr, 2);
        let v_created = dtob_ffi::ffi_dtob_array_get(val_ptr, 3);
        let v_edited = dtob_ffi::ffi_dtob_array_get(val_ptr, 4);
        
        let title_str = get_raw_str(v_title);
        let cid_str = hex::encode(get_raw_bytes(v_cid));
        let ccid_str = hex::encode(get_raw_bytes(v_ccid));
        
        let created_ts = dtob_ffi::ffi_dtob_get_u64(v_created as *mut _) as i64;
        let edited_ts = dtob_ffi::ffi_dtob_get_u64(v_edited as *mut _) as i64;

        let entries = vec![TableEntry {
            xvuid: xvuid.to_string(),
            title: title_str,
            cid: cid_str,
            ccid: ccid_str,
            created_ts,
            edited_ts,
        }];
        encode_entries_to_buf(&entries, out);
    }
}

pub fn generate_table_for_xvuid(_maps_path: &Path, cache: &crate::maps::MapsCache, xvuid: &str) {
    let mut buf = Vec::new();
    generate_table_for_xvuid_to_buf(_maps_path, cache, xvuid, &mut buf);
    use std::io::Write;
    let _ = std::io::stdout().write_all(&buf);
    let _ = std::io::stdout().flush();
}

fn encode_entries_to_buf(entries: &[TableEntry], out: &mut Vec<u8>) {
    unsafe {
        let out_array = dtob_ffi::ffi_array_new();

        for entry in entries {
            let kv = dtob_ffi::ffi_kvset_new();
            
            let put_string = |kv: *mut dtob_ffi::DtobValue, k: &str, v: &str| {
                if let Ok(k_c) = std::ffi::CString::new(k) {
                    let val = dtob_ffi::ffi_raw_new(v.as_ptr(), v.len());
                    dtob_ffi::ffi_kvset_put(kv, k_c.as_ptr(), val);
                }
            };

            let created_date = Utc.timestamp_opt(entry.created_ts, 0).unwrap().format("%d %b %Y %H:%M:%S GMT").to_string();
            let edited_date = Utc.timestamp_opt(entry.edited_ts, 0).unwrap().format("%d %b %Y %H:%M:%S GMT").to_string();

            put_string(kv, "title", &entry.title);
            put_string(kv, "xvuid", &entry.xvuid);
            put_string(kv, "cid", &entry.cid);
            put_string(kv, "ccid", &entry.ccid);
            put_string(kv, "date_created", &created_date);
            put_string(kv, "date_edited", &edited_date);

            dtob_ffi::ffi_array_push(out_array, kv);
        }

        let mut out_len = 0;
        let out_ptr = dtob_ffi::ffi_encode_dif(out_array, &mut out_len);
        if !out_ptr.is_null() && out_len > 0 {
            let out_bytes = std::slice::from_raw_parts(out_ptr, out_len);
            out.extend_from_slice(out_bytes);
        }

        dtob_ffi::ffi_dtob_free(out_array);
        if !out_ptr.is_null() {
            libc::free(out_ptr as *mut libc::c_void);
        }
    }
}

unsafe fn get_raw_bytes(val: *const dtob_ffi::DtobValue) -> Vec<u8> {
    if val.is_null() { return Vec::new(); }
    let mut len = 0;
    let ptr = dtob_ffi::ffi_dtob_get_raw(val as *mut _, &mut len);
    if ptr.is_null() || len == 0 {
        Vec::new()
    } else {
        std::slice::from_raw_parts(ptr, len).to_vec()
    }
}

unsafe fn get_raw_str(val: *const dtob_ffi::DtobValue) -> String {
    let bytes = get_raw_bytes(val);
    String::from_utf8_lossy(&bytes).to_string()
}

