use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read};

use fitparser::Value;
use pyo3::prelude::*;
use pyo3::types::PyList;

fn convert_value(py: Python<'_>, value: &Value) -> Option<PyObject> {
    match value {
        Value::Timestamp(dt) => Some(
            dt.format("%Y-%m-%dT%H:%M:%S")
                .to_string()
                .into_pyobject(py)
                .unwrap()
                .into_any()
                .unbind(),
        ),
        Value::String(s) => Some(s.into_pyobject(py).unwrap().into_any().unbind()),
        Value::Float32(f) => Some((*f as f64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::Float64(f) => Some(f.into_pyobject(py).unwrap().into_any().unbind()),
        Value::SInt8(i) => Some((*i as i64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::SInt16(i) => Some((*i as i64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::SInt32(i) => Some((*i as i64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::SInt64(i) => Some(i.into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt8(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt8z(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt16(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt16z(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt32(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt32z(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt64(i) => Some(i.into_pyobject(py).unwrap().into_any().unbind()),
        Value::UInt64z(i) => Some(i.into_pyobject(py).unwrap().into_any().unbind()),
        Value::Enum(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::Byte(i) => Some((*i as u64).into_pyobject(py).unwrap().into_any().unbind()),
        Value::Array(vec) => {
            let items: Vec<PyObject> = vec.iter().filter_map(|v| convert_value(py, v)).collect();
            Some(
                PyList::new(py, &items)
                    .unwrap()
                    .into_pyobject(py)
                    .unwrap()
                    .into_any()
                    .unbind(),
            )
        }
    }
}

/// Return a human-readable message-type name for `mesg_num`.
///
/// fitparser outputs the raw numeric string for message types that are not in
/// its built-in profile (e.g. `"13"` for `training_settings`).  When the
/// string is a plain integer we apply a FIT SDK fallback table so the key
/// matches what garmin-fit-sdk returns.
fn mesg_name(mesg_num: &fitparser::profile::field_types::MesgNum) -> String {
    let s = mesg_num.to_string();
    if let Ok(num) = s.parse::<u16>() {
        match num {
            13 => "training_settings".to_string(),
            _ => s,
        }
    } else {
        s
    }
}

fn parse_from_reader<R: Read>(
    py: Python<'_>,
    reader: &mut R,
) -> PyResult<Vec<HashMap<String, PyObject>>> {
    let records = fitparser::from_reader(reader)
        .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;

    let mut result: Vec<HashMap<String, PyObject>> = Vec::new();

    for record in records {
        let kind = mesg_name(&record.kind());
        let mut fields: HashMap<String, PyObject> = HashMap::new();

        fields.insert(
            "message_type".to_string(),
            kind.into_pyobject(py).unwrap().into_any().unbind(),
        );

        for field in record.fields() {
            if let Some(py_val) = convert_value(py, field.value()) {
                fields.insert(field.name().to_string(), py_val);
            }
        }

        // Mirror garmin-fit-sdk component expansion: derive speed/altitude from
        // their enhanced counterparts when the base fields are absent.
        if let Some(v) = fields.get("enhanced_speed").map(|v| v.clone_ref(py)) {
            fields.entry("speed".to_string()).or_insert(v);
        }
        if let Some(v) = fields.get("enhanced_altitude").map(|v| v.clone_ref(py)) {
            fields.entry("altitude".to_string()).or_insert(v);
        }

        result.push(fields);
    }

    Ok(result)
}

#[pyfunction]
fn parse_fit_file(
    py: Python<'_>,
    path: &str,
) -> PyResult<Vec<HashMap<String, PyObject>>> {
    let mut file =
        File::open(path).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?;
    parse_from_reader(py, &mut file)
}

#[pyfunction]
fn parse_fit_bytes(
    py: Python<'_>,
    data: &[u8],
) -> PyResult<Vec<HashMap<String, PyObject>>> {
    let mut cursor = Cursor::new(data);
    parse_from_reader(py, &mut cursor)
}

#[pymodule]
fn _internal(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(parse_fit_file, m)?)?;
    m.add_function(wrap_pyfunction!(parse_fit_bytes, m)?)?;
    Ok(())
}
