use crate::ast::*;
use crate::error::ParseError;
use crate::span::Span;
use crate::token::{Operator, Token, TokenKind};

/// Precedence levels for Pratt parsing (higher = tighter binding).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Precedence {
    None = 0,
    Assignment = 1,
    Conversion = 2,
    Comparison = 3,
    Sum = 4,
    Product = 5,
    Exponent = 6,
    Unary = 7,
}

/// A recursive-descent Pratt parser that consumes a token stream and produces an AST.
pub struct Parser {
    tokens: Vec<Token>,
    pos: usize,
}

impl Parser {
    pub fn new(tokens: Vec<Token>) -> Self {
        Self { tokens, pos: 0 }
    }

    /// Parse the entire token stream into an expression.
    pub fn parse(mut self) -> Result<Expr, ParseError> {
        if self.at_end() {
            return Err(ParseError::new(
                "unexpected end of input: expected an expression",
                Span::new(0, 0),
            ));
        }

        let expr = self.parse_expr(Precedence::None)?;

        // Allow trailing comments and Eof
        while self.check(&TokenKind::Eof) || matches!(self.peek().kind, TokenKind::Comment(_)) {
            self.advance();
            if self.pos >= self.tokens.len() {
                break;
            }
        }

        Ok(expr)
    }

    fn parse_expr(&mut self, min_prec: Precedence) -> Result<Expr, ParseError> {
        let mut left = self.parse_prefix()?;

        loop {
            if self.at_end() {
                break;
            }

            let tok = self.peek();
            if matches!(tok.kind, TokenKind::Eof | TokenKind::Comment(_)) {
                break;
            }

            // Check for assignment: if left is Identifier and we see `=`
            if min_prec <= Precedence::Assignment {
                if let ExprKind::Identifier(ref name) = left.node {
                    if self.check(&TokenKind::Assign) {
                        let name = name.clone();
                        let assign_span_start = left.span.start;
                        self.advance(); // consume `=`
                        let value = self.parse_expr(Precedence::Assignment)?;
                        let span = Span::new(assign_span_start, value.span.end);
                        left = Spanned::new(
                            ExprKind::Assignment {
                                name,
                                value: Box::new(value),
                            },
                            span,
                        );
                        continue;
                    }
                }
            }

            // Percent operation: `expr +/- N%`
            if matches!(
                self.peek().kind,
                TokenKind::Percent(_)
            ) {
                // Look back at the previous operator we might have consumed
                // Actually, percent is handled as infix after +/- in the binary op path
                break;
            }

            let prec = self.infix_precedence();
            if prec <= min_prec {
                break;
            }

            left = self.parse_infix(left, prec)?;
        }

        Ok(left)
    }

    fn parse_prefix(&mut self) -> Result<Expr, ParseError> {
        let tok = self.peek().clone();
        match &tok.kind {
            TokenKind::Number(val) => {
                let val = *val;
                let span = tok.span;
                self.advance();
                // Check for unit suffix
                if !self.at_end() {
                    if let TokenKind::Unit(unit) = &self.peek().kind {
                        let unit = unit.clone();
                        let unit_span = self.peek().span;
                        self.advance();
                        return Ok(Spanned::new(
                            ExprKind::UnitNumber { value: val, unit },
                            span.merge(unit_span),
                        ));
                    }
                }
                Ok(Spanned::new(ExprKind::Number(val), span))
            }

            TokenKind::CurrencySymbol(sym) => {
                let currency = symbol_to_currency(sym);
                let sym_span = tok.span;
                self.advance();
                // Expect a number after currency symbol
                if self.at_end() {
                    return Err(ParseError::new(
                        "expected number after currency symbol",
                        sym_span,
                    ));
                }
                let amount_expr = self.parse_prefix()?;
                let amount = extract_number(&amount_expr)?;
                let span = sym_span.merge(amount_expr.span);
                Ok(Spanned::new(
                    ExprKind::CurrencyValue { amount, currency },
                    span,
                ))
            }

            TokenKind::Op(Operator::Subtract) => {
                let op_span = tok.span;
                self.advance();
                let operand = self.parse_expr(Precedence::Unary)?;
                let span = op_span.merge(operand.span);
                Ok(Spanned::new(ExprKind::UnaryNeg(Box::new(operand)), span))
            }

            TokenKind::LParen => {
                let open_span = tok.span;
                self.advance();
                let inner = self.parse_expr(Precedence::None)?;
                if !self.check(&TokenKind::RParen) {
                    return Err(ParseError::new(
                        "expected closing parenthesis ')'",
                        self.current_span(),
                    ));
                }
                let close_span = self.peek().span;
                self.advance();
                // Re-wrap with the full span including parens
                Ok(Spanned::new(inner.node, open_span.merge(close_span)))
            }

            TokenKind::Identifier(name) => {
                let name = name.clone();
                let span = tok.span;
                self.advance();

                // Check for function call: Identifier followed by '('
                if !self.at_end() && self.check(&TokenKind::LParen) {
                    self.advance(); // consume '('
                    let mut args = Vec::new();
                    // Parse arguments (comma-separated)
                    if !self.check(&TokenKind::RParen) {
                        let arg = self.parse_expr(Precedence::None)?;
                        args.push(arg);
                        // For future: could add comma support here
                    }
                    if !self.check(&TokenKind::RParen) {
                        return Err(ParseError::new(
                            "expected closing parenthesis ')' after function arguments",
                            self.current_span(),
                        ));
                    }
                    let close_span = self.peek().span;
                    self.advance();
                    return Ok(Spanned::new(
                        ExprKind::FunctionCall { name, args },
                        span.merge(close_span),
                    ));
                }

                Ok(Spanned::new(ExprKind::Identifier(name), span))
            }

            TokenKind::LineRef(line_num) => {
                let line_num = *line_num;
                let span = tok.span;
                self.advance();
                Ok(Spanned::new(ExprKind::LineRef(line_num), span))
            }

            TokenKind::PrevRef => {
                let span = tok.span;
                self.advance();
                Ok(Spanned::new(ExprKind::PrevRef, span))
            }

            TokenKind::Percent(val) => {
                // Standalone percentage: 8% = 0.08
                let val = *val;
                let span = tok.span;
                self.advance();
                Ok(Spanned::new(ExprKind::Number(val / 100.0), span))
            }

            _ => Err(ParseError::new(
                format!("unexpected token: {:?}", tok.kind),
                tok.span,
            )),
        }
    }

    fn parse_infix(&mut self, left: Expr, prec: Precedence) -> Result<Expr, ParseError> {
        let tok = self.peek().clone();
        match &tok.kind {
            TokenKind::Op(op) => {
                let bin_op = operator_to_binop(op);
                self.advance();

                // Check for percent operation: `expr +/- N%`
                if matches!(bin_op, BinOp::Add | BinOp::Sub) {
                    if let Some(pct) = self.try_parse_percent() {
                        let pct_op = if matches!(bin_op, BinOp::Add) {
                            PercentOp::Add
                        } else {
                            PercentOp::Sub
                        };
                        let span = left.span.merge(Span::new(
                            self.tokens[self.pos - 1].span.start,
                            self.tokens[self.pos - 1].span.end,
                        ));
                        return Ok(Spanned::new(
                            ExprKind::PercentOp {
                                op: pct_op,
                                base: Box::new(left),
                                percentage: pct,
                            },
                            span,
                        ));
                    }
                }

                let right = self.parse_expr(prec)?;
                let span = left.span.merge(right.span);
                Ok(Spanned::new(
                    ExprKind::BinaryOp {
                        op: bin_op,
                        left: Box::new(left),
                        right: Box::new(right),
                    },
                    span,
                ))
            }

            TokenKind::Greater | TokenKind::Less | TokenKind::GreaterEq
            | TokenKind::LessEq | TokenKind::Equal | TokenKind::NotEqual => {
                let cmp_op = match &tok.kind {
                    TokenKind::Greater => CmpOp::Gt,
                    TokenKind::Less => CmpOp::Lt,
                    TokenKind::GreaterEq => CmpOp::Gte,
                    TokenKind::LessEq => CmpOp::Lte,
                    TokenKind::Equal => CmpOp::Eq,
                    TokenKind::NotEqual => CmpOp::Neq,
                    _ => unreachable!(),
                };
                self.advance();
                let right = self.parse_expr(Precedence::Comparison)?;
                let span = left.span.merge(right.span);
                Ok(Spanned::new(
                    ExprKind::Comparison {
                        op: cmp_op,
                        left: Box::new(left),
                        right: Box::new(right),
                    },
                    span,
                ))
            }

            TokenKind::In => {
                self.advance();
                // Parse conversion target (keyword or identifier)
                let target = self.parse_conversion_target()?;
                let span = left.span.merge(self.tokens[self.pos - 1].span);
                Ok(Spanned::new(
                    ExprKind::Conversion {
                        expr: Box::new(left),
                        target_currency: target,
                    },
                    span,
                ))
            }

            _ => Err(ParseError::new(
                format!("unexpected infix token: {:?}", tok.kind),
                tok.span,
            )),
        }
    }

    fn try_parse_percent(&mut self) -> Option<f64> {
        if self.at_end() {
            return None;
        }
        if let TokenKind::Percent(val) = &self.peek().kind {
            let val = *val;
            self.advance();
            // Skip optional "discount" or "off" keyword
            if !self.at_end() {
                if let TokenKind::Keyword(kw) = &self.peek().kind {
                    if kw == "discount" || kw == "off" {
                        self.advance();
                    }
                }
            }
            Some(val)
        } else {
            None
        }
    }

    fn parse_conversion_target(&mut self) -> Result<String, ParseError> {
        if self.at_end() {
            return Err(ParseError::new(
                "expected conversion target after 'in'",
                self.current_span(),
            ));
        }
        let tok = self.peek().clone();
        match &tok.kind {
            TokenKind::Keyword(kw) => {
                let target = keyword_to_currency(kw);
                self.advance();
                Ok(target)
            }
            TokenKind::Identifier(id) => {
                let target = id.to_uppercase();
                self.advance();
                Ok(target)
            }
            TokenKind::Unit(u) => {
                let target = u.clone();
                self.advance();
                Ok(target)
            }
            _ => Err(ParseError::new(
                format!("expected conversion target, got {:?}", tok.kind),
                tok.span,
            )),
        }
    }

    fn infix_precedence(&self) -> Precedence {
        if self.at_end() {
            return Precedence::None;
        }
        match &self.peek().kind {
            TokenKind::Op(Operator::Add) | TokenKind::Op(Operator::Subtract) => Precedence::Sum,
            TokenKind::Op(Operator::Multiply)
            | TokenKind::Op(Operator::Divide)
            | TokenKind::Op(Operator::Modulo) => Precedence::Product,
            TokenKind::Op(Operator::Power) => Precedence::Exponent,
            TokenKind::Greater
            | TokenKind::Less
            | TokenKind::GreaterEq
            | TokenKind::LessEq
            | TokenKind::Equal
            | TokenKind::NotEqual => Precedence::Comparison,
            TokenKind::In => Precedence::Conversion,
            _ => Precedence::None,
        }
    }

    fn peek(&self) -> &Token {
        &self.tokens[self.pos]
    }

    fn check(&self, kind: &TokenKind) -> bool {
        if self.at_end() {
            return false;
        }
        std::mem::discriminant(&self.peek().kind) == std::mem::discriminant(kind)
    }

    fn advance(&mut self) {
        if self.pos < self.tokens.len() {
            self.pos += 1;
        }
    }

    fn at_end(&self) -> bool {
        self.pos >= self.tokens.len()
    }

    fn current_span(&self) -> Span {
        if self.at_end() {
            if let Some(last) = self.tokens.last() {
                last.span
            } else {
                Span::new(0, 0)
            }
        } else {
            self.peek().span
        }
    }
}

/// Top-level parse function.
pub fn parse(tokens: Vec<Token>) -> Result<Expr, ParseError> {
    // Filter out text-only token streams
    let has_expr = tokens.iter().any(|t| {
        !matches!(
            t.kind,
            TokenKind::Text(_) | TokenKind::Comment(_) | TokenKind::Eof
        )
    });
    if !has_expr {
        return Err(ParseError::new("no expression to parse", Span::new(0, 0)));
    }
    Parser::new(tokens).parse()
}

fn operator_to_binop(op: &Operator) -> BinOp {
    match op {
        Operator::Add => BinOp::Add,
        Operator::Subtract => BinOp::Sub,
        Operator::Multiply => BinOp::Mul,
        Operator::Divide => BinOp::Div,
        Operator::Power => BinOp::Pow,
        Operator::Modulo => BinOp::Mod,
    }
}

fn symbol_to_currency(sym: &str) -> String {
    match sym {
        "$" => "USD".to_string(),
        "€" => "EUR".to_string(),
        "£" => "GBP".to_string(),
        "¥" => "JPY".to_string(),
        "R$" => "BRL".to_string(),
        other => other.to_string(),
    }
}

fn keyword_to_currency(kw: &str) -> String {
    match kw.to_lowercase().as_str() {
        "euro" | "eur" => "EUR".to_string(),
        "usd" | "dollar" | "dollars" => "USD".to_string(),
        "gbp" | "pound" | "pounds" => "GBP".to_string(),
        other => other.to_uppercase(),
    }
}

fn extract_number(expr: &Expr) -> Result<f64, ParseError> {
    match &expr.node {
        ExprKind::Number(v) => Ok(*v),
        ExprKind::UnaryNeg(inner) => {
            let v = extract_number(inner)?;
            Ok(-v)
        }
        _ => Err(ParseError::new(
            "expected numeric value",
            expr.span,
        )),
    }
}
