use std::collections::BTreeMap;

use serde_json::{Map, Number, Value};
use thiserror::Error;

use super::ast::CallExpression;

#[derive(Debug, Error)]
pub enum ParseError {
    #[error("unexpected end of input")]
    Eof,
    #[error("expected {expected} at byte {at}")]
    Expected { expected: &'static str, at: usize },
    #[error("invalid token at byte {0}")]
    InvalidToken(usize),
    #[error("invalid number `{0}`")]
    InvalidNumber(String),
    #[error("duplicate named argument `{0}`")]
    DuplicateNamedArg(String),
    #[error("positional argument cannot appear after named argument")]
    PositionalAfterNamed,
}

pub fn parse_expression(input: &str) -> Result<CallExpression, ParseError> {
    let mut p = Parser::new(input);
    let provider = p.parse_identifier()?;
    p.consume_char('.')?;
    let command = p.parse_identifier()?;
    p.consume_char('(')?;

    let mut positional_args = Vec::new();
    let mut named_args = BTreeMap::new();
    let mut named_seen = false;

    p.skip_ws();
    if !p.try_consume(')') {
        loop {
            p.skip_ws();
            let checkpoint = p.pos;
            if let Ok(name) = p.parse_identifier() {
                p.skip_ws();
                if p.try_consume('=') {
                    named_seen = true;
                    p.skip_ws();
                    let value = p.parse_value()?;
                    if named_args.insert(name.clone(), value).is_some() {
                        return Err(ParseError::DuplicateNamedArg(name));
                    }
                } else {
                    p.pos = checkpoint;
                    if named_seen {
                        return Err(ParseError::PositionalAfterNamed);
                    }
                    positional_args.push(p.parse_value()?);
                }
            } else {
                if named_seen {
                    return Err(ParseError::PositionalAfterNamed);
                }
                positional_args.push(p.parse_value()?);
            }

            p.skip_ws();
            if p.try_consume(')') {
                break;
            }
            p.consume_char(',')?;
        }
    }

    p.skip_ws();
    if !p.is_eof() {
        return Err(ParseError::InvalidToken(p.pos));
    }

    Ok(CallExpression {
        provider,
        command,
        positional_args,
        named_args: named_args.into_iter().collect(),
    })
}

struct Parser<'a> {
    chars: Vec<char>,
    pos: usize,
    _phantom: std::marker::PhantomData<&'a str>,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Self {
            chars: input.chars().collect(),
            pos: 0,
            _phantom: std::marker::PhantomData,
        }
    }

    fn is_eof(&self) -> bool {
        self.pos >= self.chars.len()
    }

    fn peek(&self) -> Option<char> {
        self.chars.get(self.pos).copied()
    }

    fn next(&mut self) -> Option<char> {
        let ch = self.peek()?;
        self.pos += 1;
        Some(ch)
    }

    fn skip_ws(&mut self) {
        while matches!(self.peek(), Some(c) if c.is_whitespace()) {
            self.pos += 1;
        }
    }

    fn consume_char(&mut self, expected: char) -> Result<(), ParseError> {
        self.skip_ws();
        match self.next() {
            Some(ch) if ch == expected => Ok(()),
            Some(_) => Err(ParseError::Expected {
                expected: expected_to_str(expected),
                at: self.pos.saturating_sub(1),
            }),
            None => Err(ParseError::Eof),
        }
    }

    fn try_consume(&mut self, expected: char) -> bool {
        self.skip_ws();
        if self.peek() == Some(expected) {
            self.pos += 1;
            true
        } else {
            false
        }
    }

    fn parse_identifier(&mut self) -> Result<String, ParseError> {
        self.skip_ws();
        let start = self.pos;
        let first = self.peek().ok_or(ParseError::Eof)?;
        if !(first.is_ascii_alphabetic() || first == '_') {
            return Err(ParseError::InvalidToken(start));
        }
        self.pos += 1;
        while let Some(ch) = self.peek() {
            if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
                self.pos += 1;
            } else {
                break;
            }
        }
        Ok(self.chars[start..self.pos].iter().collect())
    }

    fn parse_value(&mut self) -> Result<Value, ParseError> {
        self.skip_ws();
        let Some(ch) = self.peek() else {
            return Err(ParseError::Eof);
        };
        match ch {
            '"' | '\'' => self.parse_string().map(Value::String),
            '[' => self.parse_array(),
            '{' => self.parse_object(),
            '-' | '0'..='9' => self.parse_number(),
            _ if ch.is_ascii_alphabetic() => self.parse_keyword_or_identifier(),
            _ => Err(ParseError::InvalidToken(self.pos)),
        }
    }

    fn parse_string(&mut self) -> Result<String, ParseError> {
        let quote = self.next().ok_or(ParseError::Eof)?;
        let mut out = String::new();
        while let Some(ch) = self.next() {
            match ch {
                c if c == quote => return Ok(out),
                '\\' => {
                    let esc = self.next().ok_or(ParseError::Eof)?;
                    let decoded = match esc {
                        'n' => '\n',
                        'r' => '\r',
                        't' => '\t',
                        '\\' => '\\',
                        '\'' => '\'',
                        '"' => '"',
                        other => other,
                    };
                    out.push(decoded);
                }
                other => out.push(other),
            }
        }
        Err(ParseError::Eof)
    }

    fn parse_number(&mut self) -> Result<Value, ParseError> {
        let start = self.pos;
        if self.peek() == Some('-') {
            self.pos += 1;
        }
        while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
            self.pos += 1;
        }
        if self.peek() == Some('.') {
            self.pos += 1;
            while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
                self.pos += 1;
            }
        }
        if matches!(self.peek(), Some('e') | Some('E')) {
            self.pos += 1;
            if matches!(self.peek(), Some('+') | Some('-')) {
                self.pos += 1;
            }
            while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
                self.pos += 1;
            }
        }

        let token: String = self.chars[start..self.pos].iter().collect();
        if token.contains('.') || token.contains('e') || token.contains('E') {
            let num: f64 = token
                .parse()
                .map_err(|_| ParseError::InvalidNumber(token.clone()))?;
            Number::from_f64(num)
                .map(Value::Number)
                .ok_or(ParseError::InvalidNumber(token))
        } else {
            let num: i64 = token
                .parse()
                .map_err(|_| ParseError::InvalidNumber(token.clone()))?;
            Ok(Value::Number(Number::from(num)))
        }
    }

    fn parse_keyword_or_identifier(&mut self) -> Result<Value, ParseError> {
        let ident = self.parse_identifier()?;
        match ident.as_str() {
            "true" => Ok(Value::Bool(true)),
            "false" => Ok(Value::Bool(false)),
            "null" => Ok(Value::Null),
            _ => Ok(Value::String(ident)),
        }
    }

    fn parse_array(&mut self) -> Result<Value, ParseError> {
        self.consume_char('[')?;
        let mut out = Vec::new();
        self.skip_ws();
        if self.try_consume(']') {
            return Ok(Value::Array(out));
        }
        loop {
            out.push(self.parse_value()?);
            self.skip_ws();
            if self.try_consume(']') {
                break;
            }
            self.consume_char(',')?;
        }
        Ok(Value::Array(out))
    }

    fn parse_object(&mut self) -> Result<Value, ParseError> {
        self.consume_char('{')?;
        let mut out = Map::new();
        self.skip_ws();
        if self.try_consume('}') {
            return Ok(Value::Object(out));
        }
        loop {
            self.skip_ws();
            let key = self.parse_string()?;
            self.consume_char(':')?;
            let value = self.parse_value()?;
            out.insert(key, value);
            self.skip_ws();
            if self.try_consume('}') {
                break;
            }
            self.consume_char(',')?;
        }
        Ok(Value::Object(out))
    }
}

fn expected_to_str(ch: char) -> &'static str {
    match ch {
        '(' => "`(`",
        ')' => "`)`",
        '.' => "`.`",
        ',' => "`,`",
        ':' => "`:`",
        '=' => "`=`",
        '[' => "`[`",
        ']' => "`]`",
        '{' => "`{`",
        '}' => "`}`",
        _ => "token",
    }
}

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

    #[test]
    fn parse_complex_expression() {
        let expr = parse_expression(
            "github.search_issues(\"bug\", per_page=25, include_pr=false, meta={\"k\":1})",
        )
        .unwrap();
        assert_eq!(expr.provider, "github");
        assert_eq!(expr.command, "search_issues");
        assert_eq!(expr.positional_args.len(), 1);
        assert_eq!(expr.named_args.len(), 3);
    }

    #[test]
    fn positional_after_named_fails() {
        let err = parse_expression("x.y(a=1, 2)").unwrap_err();
        assert_eq!(
            err.to_string(),
            "positional argument cannot appear after named argument"
        );
    }
}
