//! Minimal RFC 8941 Structured Fields parser.
//!
//! Parses the subset needed for RFC 9421 HTTP Message Signatures:
//! - Dictionaries with inner list or byte sequence values
//! - Parameters (string, token, integer)
//! - Preserves raw inner list text for @signature-params

use thiserror::Error;

#[derive(Debug, Error)]
pub enum SfError {
    #[error("unexpected end of input")]
    UnexpectedEof,
    #[error("invalid character: {0:?}")]
    InvalidChar(char),
    #[error("invalid base64")]
    InvalidBase64,
}

/// A structured field parameter value.
#[derive(Debug, Clone, PartialEq)]
pub enum SfParam {
    String(String),
    Token(String),
    Integer(i64),
}

/// A structured field inner list item (bare value + parameters).
#[derive(Debug, Clone)]
pub struct SfItem {
    pub value: SfValue,
}

/// A structured field value.
#[derive(Debug, Clone)]
pub enum SfValue {
    InnerList(Vec<SfItem>),
    ByteSeq(Vec<u8>),
    String(String),
    #[allow(dead_code)]
    Token(String),
    #[allow(dead_code)]
    Integer(i64),
}

/// A dictionary member: value + parameters on the member itself.
#[derive(Debug, Clone)]
pub struct SfMember {
    pub value: SfValue,
    pub params: Vec<(String, SfParam)>,
    /// Raw text of the value (for inner lists, needed for @signature-params).
    pub raw_text: String,
}

/// Parse a structured field dictionary.
pub fn parse_dictionary(input: &str) -> Result<Vec<(String, SfMember)>, SfError> {
    let mut result = Vec::new();
    let mut chars = input.chars().peekable();

    while chars.peek().is_some() {
        skip_spaces(&mut chars);
        let key = parse_key(&mut chars)?;
        skip_spaces(&mut chars);

        let mut member_params = Vec::new();

        let value = if chars.peek() == Some(&'=') {
            chars.next();
            skip_spaces(&mut chars);
            let (val, mut raw) = parse_value(&mut chars)?;
            parse_member_params(&mut chars, &mut member_params)?;
            // Append member-level parameters to raw_text
            raw.push_str(&serialize_params(&member_params));
            SfMember {
                value: val,
                params: member_params,
                raw_text: raw,
            }
        } else {
            // Boolean true (member present without value)
            parse_member_params(&mut chars, &mut member_params)?;
            let raw = serialize_params(&member_params);
            SfMember {
                value: SfValue::Token("true".to_string()),
                params: member_params,
                raw_text: raw,
            }
        };

        result.push((key, value));

        skip_spaces(&mut chars);
        if chars.peek() == Some(&',') {
            chars.next();
        }
    }

    Ok(result)
}

fn skip_spaces(chars: &mut std::iter::Peekable<std::str::Chars>) {
    while let Some(&c) = chars.peek() {
        if c == ' ' || c == '\t' {
            chars.next();
        } else {
            break;
        }
    }
}

fn parse_key(chars: &mut std::iter::Peekable<std::str::Chars>) -> Result<String, SfError> {
    let mut key = String::new();
    while let Some(&c) = chars.peek() {
        if c.is_ascii_lowercase()
            || c.is_ascii_digit()
            || c == '_'
            || c == '-'
            || c == '.'
            || c == '*'
        {
            key.push(c);
            chars.next();
        } else {
            break;
        }
    }
    if key.is_empty() {
        return Err(SfError::UnexpectedEof);
    }
    Ok(key)
}

fn parse_value(
    chars: &mut std::iter::Peekable<std::str::Chars>,
) -> Result<(SfValue, String), SfError> {
    let mut raw = String::new();

    match chars.peek() {
        Some(&'"') => {
            let s = parse_string(chars, &mut raw)?;
            Ok((SfValue::String(s), raw))
        }
        Some(&':') => {
            let bytes = parse_byte_seq(chars, &mut raw)?;
            Ok((SfValue::ByteSeq(bytes), raw))
        }
        Some(&'(') => {
            let items = parse_inner_list(chars, &mut raw)?;
            Ok((SfValue::InnerList(items), raw))
        }
        Some(&c) if c.is_ascii_digit() || c == '-' => {
            let i = parse_integer(chars, &mut raw)?;
            Ok((SfValue::Integer(i), raw))
        }
        Some(&c) if c.is_ascii_alphabetic() || c == '*' => {
            let t = parse_token(chars, &mut raw)?;
            Ok((SfValue::Token(t), raw))
        }
        Some(&c) => Err(SfError::InvalidChar(c)),
        None => Err(SfError::UnexpectedEof),
    }
}

fn parse_string(
    chars: &mut std::iter::Peekable<std::str::Chars>,
    raw: &mut String,
) -> Result<String, SfError> {
    chars.next(); // opening quote
    raw.push('"');
    let mut s = String::new();
    let mut escape = false;

    for c in chars.by_ref() {
        raw.push(c);
        if escape {
            s.push(c);
            escape = false;
        } else if c == '\\' {
            escape = true;
        } else if c == '"' {
            return Ok(s);
        } else {
            s.push(c);
        }
    }

    Err(SfError::UnexpectedEof)
}

fn parse_byte_seq(
    chars: &mut std::iter::Peekable<std::str::Chars>,
    raw: &mut String,
) -> Result<Vec<u8>, SfError> {
    chars.next(); // opening colon
    raw.push(':');
    let mut b64 = String::new();

    while let Some(&c) = chars.peek() {
        if c == ':' {
            chars.next();
            raw.push(':');
            return base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
                .map_err(|_| SfError::InvalidBase64);
        }
        b64.push(c);
        raw.push(c);
        chars.next();
    }

    Err(SfError::UnexpectedEof)
}

fn parse_inner_list(
    chars: &mut std::iter::Peekable<std::str::Chars>,
    raw: &mut String,
) -> Result<Vec<SfItem>, SfError> {
    chars.next(); // opening paren
    raw.push('(');
    let mut items = Vec::new();
    let mut first = true;

    loop {
        skip_spaces(chars);
        if chars.peek() == Some(&')') {
            chars.next();
            raw.push(')');
            return Ok(items);
        }

        // Add space before items (except the first)
        if !first {
            raw.push(' ');
        }
        first = false;

        let (val, item_raw) = parse_value(chars)?;
        raw.push_str(&item_raw);

        items.push(SfItem { value: val });

        skip_spaces(chars);
    }
}

fn parse_integer(
    chars: &mut std::iter::Peekable<std::str::Chars>,
    raw: &mut String,
) -> Result<i64, SfError> {
    let mut s = String::new();
    if let Some(&'-') = chars.peek() {
        s.push('-');
        raw.push('-');
        chars.next();
    }

    while let Some(&c) = chars.peek() {
        if c.is_ascii_digit() {
            s.push(c);
            raw.push(c);
            chars.next();
        } else {
            break;
        }
    }

    s.parse().map_err(|_| SfError::UnexpectedEof)
}

fn parse_token(
    chars: &mut std::iter::Peekable<std::str::Chars>,
    raw: &mut String,
) -> Result<String, SfError> {
    let mut s = String::new();
    while let Some(&c) = chars.peek() {
        if c.is_ascii_alphanumeric()
            || c == '_'
            || c == '-'
            || c == '.'
            || c == ':'
            || c == '/'
            || c == '*'
        {
            s.push(c);
            raw.push(c);
            chars.next();
        } else {
            break;
        }
    }
    if s.is_empty() {
        return Err(SfError::UnexpectedEof);
    }
    Ok(s)
}

fn parse_member_params(
    chars: &mut std::iter::Peekable<std::str::Chars>,
    params: &mut Vec<(String, SfParam)>,
) -> Result<(), SfError> {
    while chars.peek() == Some(&';') {
        chars.next();
        skip_spaces(chars);
        let key = parse_key(chars)?;
        skip_spaces(chars);

        let value = if chars.peek() == Some(&'=') {
            chars.next();
            skip_spaces(chars);
            parse_param_value(chars)?
        } else {
            SfParam::Token("true".to_string())
        };

        params.push((key, value));
    }
    Ok(())
}

fn parse_param_value(chars: &mut std::iter::Peekable<std::str::Chars>) -> Result<SfParam, SfError> {
    let mut raw = String::new();

    match chars.peek() {
        Some(&'"') => {
            let s = parse_string(chars, &mut raw)?;
            Ok(SfParam::String(s))
        }
        Some(&c) if c.is_ascii_digit() || c == '-' => {
            let mut s = String::new();
            if c == '-' {
                s.push(c);
                chars.next();
            }
            while let Some(&c) = chars.peek() {
                if c.is_ascii_digit() {
                    s.push(c);
                    chars.next();
                } else {
                    break;
                }
            }
            let i: i64 = s.parse().map_err(|_| SfError::UnexpectedEof)?;
            Ok(SfParam::Integer(i))
        }
        Some(&c) if c.is_ascii_alphabetic() || c == '*' => {
            let t = parse_token(chars, &mut raw)?;
            Ok(SfParam::Token(t))
        }
        Some(&c) => Err(SfError::InvalidChar(c)),
        None => Err(SfError::UnexpectedEof),
    }
}

/// Serialize parameters back to their string form (for @signature-params).
fn serialize_params(params: &[(String, SfParam)]) -> String {
    let mut result = String::new();
    for (key, value) in params {
        result.push(';');
        result.push_str(key);
        match value {
            SfParam::String(s) => {
                result.push('=');
                result.push('"');
                // Escape backslashes and quotes
                for c in s.chars() {
                    match c {
                        '\\' => result.push_str("\\\\"),
                        '"' => result.push_str("\\\""),
                        _ => result.push(c),
                    }
                }
                result.push('"');
            }
            SfParam::Integer(i) => {
                result.push('=');
                result.push_str(&i.to_string());
            }
            SfParam::Token(t) => {
                result.push('=');
                result.push_str(t);
            }
        }
    }
    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_simple_dictionary() {
        let input = r#"sig1=("@method" "@path");created=1234;keyid="test-key""#;
        let dict = parse_dictionary(input).unwrap();
        assert_eq!(dict.len(), 1);
        let (key, member) = &dict[0];
        assert_eq!(key, "sig1");
        assert_eq!(member.params.len(), 2);
        assert_eq!(member.params[0].0, "created");
        assert_eq!(member.params[0].1, SfParam::Integer(1234));
        assert_eq!(member.params[1].0, "keyid");
        assert_eq!(member.params[1].1, SfParam::String("test-key".to_string()));
    }

    #[test]
    fn parse_inner_list() {
        let input = r#"sig1=("@method" "host" "date");created=1234"#;
        let dict = parse_dictionary(input).unwrap();
        let (_, member) = &dict[0];
        if let SfValue::InnerList(items) = &member.value {
            assert_eq!(items.len(), 3);
        } else {
            panic!("expected inner list");
        }
    }

    #[test]
    fn parse_byte_sequence() {
        let input = r#"sig1=:dGVzdA==:"#;
        let dict = parse_dictionary(input).unwrap();
        let (_, member) = &dict[0];
        if let SfValue::ByteSeq(bytes) = &member.value {
            assert_eq!(bytes, b"test");
        } else {
            panic!("expected byte sequence");
        }
    }
}
