//! Parse `#[redline(...)]` attributes into constraint structs.

use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitInt, LitStr, Result, Token};

use crate::cost::CostProfile;

/// A parsed redline constraint.
#[derive(Debug, Clone)]
pub enum Constraint {
    /// Latency bound: `latency = "< 1ms"` (or `"< 1000cycles"` with cycle model)
    Latency(LatencyBound),
    /// Throughput bound: `throughput = "> 1GB/s"`
    Throughput(ThroughputBound),
    /// Allocation limit: `allocs = 0` or `allocs = "< 5"`
    Allocs(u64),
    /// Syscall limit: `syscalls = 0`
    Syscalls(u64),
    /// Max stack usage: `max_stack = "< 4KB"`
    MaxStack(u64),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LatencyUnit {
    /// Time-based units (ns, us, ms, s) — stored as nanoseconds
    Time,
    /// Clock cycle count
    Cycles,
}

#[derive(Debug, Clone)]
pub struct LatencyBound {
    pub nanos: u64,
    pub unit: LatencyUnit,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ThroughputBound {
    pub bytes_per_sec: u64,
}

/// All constraints from a single `#[redline(...)]` attribute.
#[derive(Debug)]
pub struct RedlineAttr {
    pub constraints: Vec<Constraint>,
    pub cost_profile: CostProfile,
}

impl Parse for RedlineAttr {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut constraints = Vec::new();
        let mut cost_profile = CostProfile::default();

        while !input.is_empty() {
            let key: Ident = input.parse()?;
            let _eq: Token![=] = input.parse()?;

            match key.to_string().as_str() {
                "latency" => {
                    let val: LitStr = input.parse()?;
                    let bound =
                        parse_latency(&val.value()).map_err(|e| syn::Error::new(val.span(), e))?;
                    constraints.push(Constraint::Latency(bound));
                }
                "throughput" => {
                    let val: LitStr = input.parse()?;
                    let bound = parse_throughput(&val.value())
                        .map_err(|e| syn::Error::new(val.span(), e))?;
                    constraints.push(Constraint::Throughput(bound));
                }
                "allocs" => {
                    if input.peek(LitStr) {
                        let val: LitStr = input.parse()?;
                        let n = parse_limit(&val.value())
                            .map_err(|e| syn::Error::new(val.span(), e))?;
                        constraints.push(Constraint::Allocs(n));
                    } else {
                        let val: LitInt = input.parse()?;
                        constraints.push(Constraint::Allocs(val.base10_parse()?));
                    }
                }
                "syscalls" => {
                    if input.peek(LitStr) {
                        let val: LitStr = input.parse()?;
                        let n = parse_limit(&val.value())
                            .map_err(|e| syn::Error::new(val.span(), e))?;
                        constraints.push(Constraint::Syscalls(n));
                    } else {
                        let val: LitInt = input.parse()?;
                        constraints.push(Constraint::Syscalls(val.base10_parse()?));
                    }
                }
                "max_stack" => {
                    let val: LitStr = input.parse()?;
                    let bytes =
                        parse_bytes(&val.value()).map_err(|e| syn::Error::new(val.span(), e))?;
                    constraints.push(Constraint::MaxStack(bytes));
                }
                "cost_model" => {
                    let val: LitStr = input.parse()?;
                    cost_profile = parse_cost_model(&val.value())
                        .map_err(|e| syn::Error::new(val.span(), e))?;
                }
                other => {
                    return Err(syn::Error::new(
                        key.span(),
                        format!("unknown redline constraint: `{}`", other),
                    ));
                }
            }

            // Optional comma separator
            if input.peek(Token![,]) {
                let _comma: Token![,] = input.parse()?;
            }
        }

        if constraints.is_empty() {
            return Err(input.error("redline requires at least one constraint"));
        }

        // Validate unit coherence: cycles unit requires cycles cost model
        for c in &constraints {
            if let Constraint::Latency(b) = c {
                if b.unit == LatencyUnit::Cycles && cost_profile != CostProfile::Cycles {
                    return Err(
                        input.error("latency unit `cycles` requires `cost_model = \"cycles\"`")
                    );
                }
                if b.unit == LatencyUnit::Time && cost_profile == CostProfile::Cycles {
                    return Err(input.error(
                        "time-based latency units (ns, us, ms, s) cannot be used with `cost_model = \"cycles\"`. Use `cycles` unit instead"
                    ));
                }
            }
        }

        Ok(RedlineAttr {
            constraints,
            cost_profile,
        })
    }
}

/// Parse cost_model value: "ns", "cycles"
fn parse_cost_model(s: &str) -> std::result::Result<CostProfile, String> {
    match s.trim() {
        "ns" | "nanoseconds" => Ok(CostProfile::Nanoseconds),
        "cycles" | "clock_cycles" => Ok(CostProfile::Cycles),
        other => Err(format!(
            "unknown cost model: `{}`. Use \"ns\" or \"cycles\"",
            other
        )),
    }
}

/// Parse "< 1ms", "< 100us", "< 5ns", "< 2s", "< 1000cycles"
fn parse_latency(s: &str) -> std::result::Result<LatencyBound, String> {
    let s = s.trim();
    let s = s.strip_prefix('<').ok_or("latency must start with '<'")?;
    let s = s.trim();

    let (num_str, unit) = split_number_unit(s)?;
    let num: f64 = num_str
        .parse()
        .map_err(|_| format!("invalid number: {}", num_str))?;

    let (nanos, lat_unit) = match unit {
        "ns" => (num as u64, LatencyUnit::Time),
        "us" => ((num * 1_000.0) as u64, LatencyUnit::Time),
        "ms" => ((num * 1_000_000.0) as u64, LatencyUnit::Time),
        "s" => ((num * 1_000_000_000.0) as u64, LatencyUnit::Time),
        "cycles" => (num as u64, LatencyUnit::Cycles),
        _ => {
            return Err(format!(
                "unknown time unit: {}. Use ns, us, ms, s, or cycles",
                unit
            ))
        }
    };

    Ok(LatencyBound {
        nanos,
        unit: lat_unit,
    })
}

/// Parse "> 1GB/s", "> 500MB/s"
fn parse_throughput(s: &str) -> std::result::Result<ThroughputBound, String> {
    let s = s.trim();
    let s = s
        .strip_prefix('>')
        .ok_or("throughput must start with '>'")?;
    let s = s.trim();
    let s = s
        .strip_suffix("/s")
        .ok_or("throughput must end with '/s'")?;

    let (num_str, unit) = split_number_unit(s)?;
    let num: f64 = num_str
        .parse()
        .map_err(|_| format!("invalid number: {}", num_str))?;

    let bytes_per_sec = match unit {
        "B" => num as u64,
        "KB" => (num * 1_024.0) as u64,
        "MB" => (num * 1_048_576.0) as u64,
        "GB" => (num * 1_073_741_824.0) as u64,
        "TB" => (num * 1_099_511_627_776.0) as u64,
        _ => {
            return Err(format!(
                "unknown size unit: {}. Use B, KB, MB, GB, TB",
                unit
            ))
        }
    };

    Ok(ThroughputBound { bytes_per_sec })
}

/// Parse "< 5" -> 5
fn parse_limit(s: &str) -> std::result::Result<u64, String> {
    let s = s.trim();
    let s = s.strip_prefix('<').ok_or("limit must start with '<'")?;
    let s = s.trim();
    s.parse().map_err(|_| format!("invalid number: {}", s))
}

/// Parse "< 4KB" -> bytes
fn parse_bytes(s: &str) -> std::result::Result<u64, String> {
    let s = s.trim();
    let s = s.strip_prefix('<').ok_or("max_stack must start with '<'")?;
    let s = s.trim();

    let (num_str, unit) = split_number_unit(s)?;
    let num: f64 = num_str
        .parse()
        .map_err(|_| format!("invalid number: {}", num_str))?;

    let bytes = match unit {
        "B" => num as u64,
        "KB" => (num * 1_024.0) as u64,
        "MB" => (num * 1_048_576.0) as u64,
        "GB" => (num * 1_073_741_824.0) as u64,
        _ => return Err(format!("unknown size unit: {}", unit)),
    };

    Ok(bytes)
}

/// Split "100us" -> ("100", "us")
fn split_number_unit(s: &str) -> std::result::Result<(&str, &str), String> {
    let idx = s
        .find(|c: char| c.is_alphabetic())
        .ok_or_else(|| format!("missing unit in: {}", s))?;
    Ok((&s[..idx], &s[idx..]))
}
