//! Formula expression parser.
//!
//! Implements a Pratt parser for the formula expression language with support for:
//! - Field references: $FieldName
//! - XBRL concept references: #taxonomy.concept[unit]
//! - Numeric literals: 123, 45.67
//! - Binary operators: +, -, *, /, %
//! - Unary operators: +, -
//! - Parentheses: ( )
//! - Function calls: FUNC(arg1, arg2, ...)

use crate::mappings::formula::ast::{BinaryOperator, ConceptReference, Expr, UnaryOperator};
use crate::mappings::formula::FormulaError;
use crate::mappings::types::TargetField;
use crate::terminal::sec_edgar::UnitFamily;
use std::iter::Peekable;
use std::str::Chars;

/// Parser for formula expressions.
pub struct FormulaParser<'a> {
    _input: &'a str,
    chars: Peekable<Chars<'a>>,
    pos: usize,
}

impl<'a> FormulaParser<'a> {
    /// Create a new parser for the given input.
    pub fn new(input: &'a str) -> Self {
        Self {
            _input: input,
            chars: input.chars().peekable(),
            pos: 0,
        }
    }

    /// Parse a formula expression string into an AST.
    pub fn parse(input: &'a str) -> Result<Expr, FormulaError> {
        let mut parser = Self::new(input);
        parser.skip_whitespace();

        if parser.is_eof() {
            return Err(FormulaError::EmptyExpression);
        }

        let expr = parser.parse_expression()?;

        parser.skip_whitespace();
        if !parser.is_eof() {
            return Err(FormulaError::Syntax {
                pos: parser.pos,
                message: format!("Unexpected character: '{:?}'", parser.peek_char()),
            });
        }

        Ok(expr)
    }

    /// Parse an expression (starts at lowest precedence).
    fn parse_expression(&mut self) -> Result<Expr, FormulaError> {
        self.parse_binary_op(0)
    }

    /// Parse binary operations with precedence climbing.
    fn parse_binary_op(&mut self, min_precedence: u8) -> Result<Expr, FormulaError> {
        let mut left = self.parse_unary()?;

        loop {
            self.skip_whitespace();

            // Check if this is a binary operator
            let op = match self.peek_char() {
                Some('+') => Some(BinaryOperator::Add),
                Some('-') => Some(BinaryOperator::Subtract),
                Some('*') => Some(BinaryOperator::Multiply),
                Some('/') => Some(BinaryOperator::Divide),
                Some('%') => Some(BinaryOperator::Modulo),
                _ => None,
            };

            let op_prec = match &op {
                Some(op) => op.precedence(),
                None => break,
            };

            // Only continue if this operator has higher precedence than minimum
            if op_prec < min_precedence {
                break;
            }

            // Consume the operator
            self.consume_char();
            let op = op.unwrap();

            // Parse the right side with higher precedence
            let right = self.parse_binary_op(op_prec + 1)?;

            left = Expr::BinaryOp {
                op,
                left: Box::new(left),
                right: Box::new(right),
            };
        }

        Ok(left)
    }

    /// Parse unary operations (+, -).
    fn parse_unary(&mut self) -> Result<Expr, FormulaError> {
        self.skip_whitespace();

        match self.peek_char() {
            Some('+') => {
                self.consume_char();
                Ok(Expr::UnaryOp {
                    op: UnaryOperator::Plus,
                    operand: Box::new(self.parse_unary()?),
                })
            }
            Some('-') => {
                self.consume_char();
                Ok(Expr::UnaryOp {
                    op: UnaryOperator::Minus,
                    operand: Box::new(self.parse_unary()?),
                })
            }
            _ => self.parse_primary(),
        }
    }

    /// Parse primary expressions (literals, field refs, concept refs, parentheses, function calls).
    fn parse_primary(&mut self) -> Result<Expr, FormulaError> {
        self.skip_whitespace();

        match self.peek_char() {
            Some('$') => self.parse_field_ref(),
            Some('#') => self.parse_concept_ref(),
            Some('0'..='9' | '.') => self.parse_number(),
            Some('(') => self.parse_parenthesized(),
            Some(c) if c.is_ascii_alphabetic() || c == '_' => self.parse_function_call(),
            Some(c) => Err(FormulaError::Syntax {
                pos: self.pos,
                message: format!("Unexpected character: '{}'", c),
            }),
            None => Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Unexpected end of input".to_string(),
            }),
        }
    }

    /// Parse a field reference: $FieldName.
    fn parse_field_ref(&mut self) -> Result<Expr, FormulaError> {
        self.consume_char(); // consume '$'

        let _start = self.pos;
        let mut field_name = String::new();

        // Read the field name (alphanumeric and underscores)
        while let Some(&c) = self.chars.peek() {
            if c.is_ascii_alphanumeric() || c == '_' {
                field_name.push(c);
                self.consume_char();
            } else {
                break;
            }
        }

        if field_name.is_empty() {
            return Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Field reference cannot be empty".to_string(),
            });
        }

        // Try to parse as TargetField (snake_case or with underscores)
        let field = field_name
            .parse::<TargetField>()
            .map_err(|_| FormulaError::UnknownField(field_name))?;

        Ok(Expr::FieldRef(field))
    }

    /// Parse a concept reference: #taxonomy.concept[unit].
    fn parse_concept_ref(&mut self) -> Result<Expr, FormulaError> {
        self.consume_char(); // consume '#'

        // Parse taxonomy
        let taxonomy = self.parse_identifier()?;

        // Expect '.'
        if self.peek_char() != Some('.') {
            return Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Expected '.' after taxonomy in concept reference".to_string(),
            });
        }
        self.consume_char();

        // Parse concept name
        let concept = self.parse_identifier()?;

        // Expect '['
        if self.peek_char() != Some('[') {
            return Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Expected '[unit_family]' after concept name".to_string(),
            });
        }
        self.consume_char();

        // Parse unit family
        let unit_family_str = self.parse_identifier()?;
        let unit_family =
            UnitFamily::from_str(&unit_family_str).map_err(|_| FormulaError::Syntax {
                pos: self.pos,
                message: format!("Unknown unit family: {}", unit_family_str),
            })?;

        // Expect ']'
        if self.peek_char() != Some(']') {
            return Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Expected ']' to close unit family specification".to_string(),
            });
        }
        self.consume_char();

        Ok(Expr::ConceptRef(ConceptReference {
            taxonomy,
            concept,
            unit_family,
        }))
    }

    /// Parse an identifier (alphanumeric, underscores, and hyphens).
    fn parse_identifier(&mut self) -> Result<String, FormulaError> {
        let start = self.pos;
        let mut identifier = String::new();

        while let Some(&c) = self.chars.peek() {
            if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
                identifier.push(c);
                self.consume_char();
            } else {
                break;
            }
        }

        if identifier.is_empty() {
            Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Expected identifier".to_string(),
            })
        } else if identifier.starts_with('-') || identifier.ends_with('-') {
            Err(FormulaError::Syntax {
                pos: start,
                message: format!("Identifier cannot start or end with '-': {}", identifier),
            })
        } else if identifier.contains("--") {
            Err(FormulaError::Syntax {
                pos: start,
                message: format!(
                    "Identifier cannot contain consecutive hyphens: {}",
                    identifier
                ),
            })
        } else {
            Ok(identifier)
        }
    }

    /// Parse a numeric literal.
    fn parse_number(&mut self) -> Result<Expr, FormulaError> {
        let start = self.pos;
        let mut num_str = String::new();

        // Parse optional sign
        if let Some(&c) = self.chars.peek() {
            if c == '+' || c == '-' {
                num_str.push(c);
                self.consume_char();
            }
        }

        // Parse integer part
        while let Some(&c) = self.chars.peek() {
            if c.is_ascii_digit() {
                num_str.push(c);
                self.consume_char();
            } else {
                break;
            }
        }

        // Parse fractional part
        if let Some(&'.') = self.chars.peek() {
            num_str.push('.');
            self.consume_char();

            while let Some(&c) = self.chars.peek() {
                if c.is_ascii_digit() {
                    num_str.push(c);
                    self.consume_char();
                } else {
                    break;
                }
            }
        }

        // Parse exponent
        if let Some(&c) = self.chars.peek() {
            if c == 'e' || c == 'E' {
                num_str.push(c);
                self.consume_char();

                if let Some(&c) = self.chars.peek() {
                    if c == '+' || c == '-' {
                        num_str.push(c);
                        self.consume_char();
                    }
                }

                while let Some(&c) = self.chars.peek() {
                    if c.is_ascii_digit() {
                        num_str.push(c);
                        self.consume_char();
                    } else {
                        break;
                    }
                }
            }
        }

        num_str
            .parse::<f64>()
            .map(Expr::Literal)
            .map_err(|_| FormulaError::Syntax {
                pos: start,
                message: format!("Invalid number: {}", num_str),
            })
    }

    /// Parse a parenthesized expression: (expr).
    fn parse_parenthesized(&mut self) -> Result<Expr, FormulaError> {
        self.consume_char(); // consume '('

        let expr = self.parse_expression()?;

        self.skip_whitespace();
        if self.peek_char() != Some(')') {
            return Err(FormulaError::Syntax {
                pos: self.pos,
                message: "Expected ')'".to_string(),
            });
        }

        self.consume_char(); // consume ')'

        Ok(expr)
    }

    /// Parse a function call: FUNC(arg1, arg2, ...).
    fn parse_function_call(&mut self) -> Result<Expr, FormulaError> {
        // Read function name
        let _start = self.pos;
        let mut name = String::new();

        while let Some(&c) = self.chars.peek() {
            if c.is_ascii_alphanumeric() || c == '_' {
                name.push(c);
                self.consume_char();
            } else {
                break;
            }
        }

        self.skip_whitespace();

        // Expect '('
        if self.peek_char() != Some('(') {
            // If not followed by '(', this might be an unknown field ref without '$'
            return Err(FormulaError::Syntax {
                pos: self.pos,
                message: format!(
                    "Expected '(' after function name '{}', or use ${} for field reference",
                    name, name
                ),
            });
        }

        self.consume_char(); // consume '('

        // Parse arguments
        let mut args = Vec::new();
        self.skip_whitespace();

        if self.peek_char() != Some(')') {
            loop {
                args.push(self.parse_expression()?);
                self.skip_whitespace();

                match self.peek_char() {
                    Some(',') => {
                        self.consume_char();
                        self.skip_whitespace();
                    }
                    Some(')') => break,
                    Some(c) => {
                        return Err(FormulaError::Syntax {
                            pos: self.pos,
                            message: format!("Expected ',' or ')', found '{}'", c),
                        });
                    }
                    None => {
                        return Err(FormulaError::Syntax {
                            pos: self.pos,
                            message: "Unexpected end of input in function call".to_string(),
                        });
                    }
                }
            }
        }

        self.consume_char(); // consume ')'

        Ok(Expr::FunctionCall { name, args })
    }

    /// Peek at the next character without consuming it.
    fn peek_char(&mut self) -> Option<char> {
        self.chars.peek().copied()
    }

    /// Consume the next character.
    fn consume_char(&mut self) -> char {
        let c = self.chars.next().unwrap();
        self.pos += 1;
        c
    }

    /// Skip whitespace characters.
    fn skip_whitespace(&mut self) {
        while let Some(&c) = self.chars.peek() {
            if c.is_ascii_whitespace() {
                self.consume_char();
            } else {
                break;
            }
        }
    }

    /// Check if we're at the end of input.
    fn is_eof(&mut self) -> bool {
        self.chars.peek().is_none()
    }
}

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

    #[test]
    fn test_parse_literal() {
        let expr = FormulaParser::parse("42").unwrap();
        assert_eq!(expr, Expr::Literal(42.0));

        let expr = FormulaParser::parse("2.72").unwrap();
        assert_eq!(expr, Expr::Literal(2.72));

        let expr = FormulaParser::parse("-5.5").unwrap();
        assert_eq!(
            expr,
            Expr::UnaryOp {
                op: UnaryOperator::Minus,
                operand: Box::new(Expr::Literal(5.5)),
            }
        );
    }

    #[test]
    fn test_parse_field_ref() {
        let expr = FormulaParser::parse("$total_revenues").unwrap();
        assert_eq!(expr, Expr::FieldRef(TargetField::TotalRevenues));

        let expr = FormulaParser::parse("$net_income").unwrap();
        assert_eq!(expr, Expr::FieldRef(TargetField::NetIncome));
    }

    #[test]
    fn test_parse_binary_op() {
        let expr = FormulaParser::parse("$total_revenues + $net_income").unwrap();
        match expr {
            Expr::BinaryOp { op, left, right } => {
                assert_eq!(op, BinaryOperator::Add);
                assert_eq!(*left, Expr::FieldRef(TargetField::TotalRevenues));
                assert_eq!(*right, Expr::FieldRef(TargetField::NetIncome));
            }
            _ => panic!("Expected BinaryOp"),
        }
    }

    #[test]
    fn test_parse_precedence() {
        // Multiplication should bind tighter than addition
        let expr = FormulaParser::parse("1 + 2 * 3").unwrap();
        match expr {
            Expr::BinaryOp { op, left, right } => {
                assert_eq!(op, BinaryOperator::Add);
                assert_eq!(*left, Expr::Literal(1.0));
                // Right side should be (2 * 3)
                match *right {
                    Expr::BinaryOp {
                        op: op2,
                        left: left2,
                        right: right2,
                    } => {
                        assert_eq!(op2, BinaryOperator::Multiply);
                        assert_eq!(*left2, Expr::Literal(2.0));
                        assert_eq!(*right2, Expr::Literal(3.0));
                    }
                    _ => panic!("Expected BinaryOp for right side"),
                }
            }
            _ => panic!("Expected BinaryOp"),
        }
    }

    #[test]
    fn test_parse_parentheses() {
        let expr = FormulaParser::parse("(1 + 2) * 3").unwrap();
        match expr {
            Expr::BinaryOp { op, left, right } => {
                assert_eq!(op, BinaryOperator::Multiply);
                // Left side should be (1 + 2)
                match *left {
                    Expr::BinaryOp {
                        op: op2,
                        left: left2,
                        right: right2,
                    } => {
                        assert_eq!(op2, BinaryOperator::Add);
                        assert_eq!(*left2, Expr::Literal(1.0));
                        assert_eq!(*right2, Expr::Literal(2.0));
                    }
                    _ => panic!("Expected BinaryOp for left side"),
                }
                assert_eq!(*right, Expr::Literal(3.0));
            }
            _ => panic!("Expected BinaryOp"),
        }
    }

    #[test]
    fn test_parse_function_call() {
        let expr = FormulaParser::parse("SUM($total_revenues, $net_income)").unwrap();
        match expr {
            Expr::FunctionCall { name, args } => {
                assert_eq!(name, "SUM");
                assert_eq!(args.len(), 2);
                assert_eq!(args[0], Expr::FieldRef(TargetField::TotalRevenues));
                assert_eq!(args[1], Expr::FieldRef(TargetField::NetIncome));
            }
            _ => panic!("Expected FunctionCall"),
        }
    }

    #[test]
    fn test_parse_complex_formula() {
        let expr = FormulaParser::parse("($gross_profit / $total_revenues) * 100").unwrap();
        match expr {
            Expr::BinaryOp { op, left, right } => {
                assert_eq!(op, BinaryOperator::Multiply);
                assert_eq!(*right, Expr::Literal(100.0));
                // Left side should be (gross_profit / total_revenues)
                match *left {
                    Expr::BinaryOp {
                        op: op2,
                        left: left2,
                        right: right2,
                    } => {
                        assert_eq!(op2, BinaryOperator::Divide);
                        assert_eq!(*left2, Expr::FieldRef(TargetField::GrossProfit));
                        assert_eq!(*right2, Expr::FieldRef(TargetField::TotalRevenues));
                    }
                    _ => panic!("Expected BinaryOp for left side"),
                }
            }
            _ => panic!("Expected BinaryOp"),
        }
    }

    #[test]
    fn test_parse_unary_minus() {
        let expr = FormulaParser::parse("-$total_revenues").unwrap();
        match expr {
            Expr::UnaryOp { op, operand } => {
                assert_eq!(op, UnaryOperator::Minus);
                assert_eq!(*operand, Expr::FieldRef(TargetField::TotalRevenues));
            }
            _ => panic!("Expected UnaryOp"),
        }
    }

    #[test]
    fn test_parse_error_empty() {
        let result = FormulaParser::parse("");
        assert!(matches!(result, Err(FormulaError::EmptyExpression)));
    }

    #[test]
    fn test_parse_error_unknown_field() {
        let result = FormulaParser::parse("$unknown_field");
        assert!(matches!(result, Err(FormulaError::UnknownField(_))));
    }

    #[test]
    fn test_parse_error_unmatched_paren() {
        let result = FormulaParser::parse("(1 + 2");
        assert!(matches!(result, Err(FormulaError::Syntax { .. })));
    }

    #[test]
    fn test_parse_concept_ref() {
        let expr = FormulaParser::parse("#us-gaap.Revenue[currency]").unwrap();
        match expr {
            Expr::ConceptRef(concept_ref) => {
                assert_eq!(concept_ref.taxonomy, "us-gaap");
                assert_eq!(concept_ref.concept, "Revenue");
                assert_eq!(concept_ref.unit_family, UnitFamily::Currency);
            }
            _ => panic!("Expected ConceptRef"),
        }
    }

    #[test]
    fn test_parse_concept_ref_with_underscores() {
        let expr =
            FormulaParser::parse("#us-gaap.RevenueFromContractsWithCustomers[currency]").unwrap();
        match expr {
            Expr::ConceptRef(concept_ref) => {
                assert_eq!(concept_ref.taxonomy, "us-gaap");
                assert_eq!(concept_ref.concept, "RevenueFromContractsWithCustomers");
                assert_eq!(concept_ref.unit_family, UnitFamily::Currency);
            }
            _ => panic!("Expected ConceptRef"),
        }
    }

    #[test]
    fn test_parse_mixed_formula() {
        let expr = FormulaParser::parse(
            "($gross_profit + #us-gaap.OtherExpenses[currency]) / $total_revenues",
        )
        .unwrap();
        match expr {
            Expr::BinaryOp { op, left, right } => {
                assert_eq!(op, BinaryOperator::Divide);
                match *left {
                    Expr::BinaryOp {
                        op: op2,
                        left: left2,
                        right: right2,
                    } => {
                        assert_eq!(op2, BinaryOperator::Add);
                        assert!(matches!(*left2, Expr::FieldRef(TargetField::GrossProfit)));
                        assert!(matches!(*right2, Expr::ConceptRef(_)));
                    }
                    _ => panic!("Expected BinaryOp for left side"),
                }
                assert!(matches!(*right, Expr::FieldRef(TargetField::TotalRevenues)));
            }
            _ => panic!("Expected BinaryOp"),
        }
    }

    #[test]
    fn test_parse_concept_only_formula() {
        let expr = FormulaParser::parse("(#us-gaap.GrossProfit[currency] / #us-gaap.RevenueFromContractsWithCustomers[currency]) * 100").unwrap();
        match expr {
            Expr::BinaryOp { op, left, right } => {
                assert_eq!(op, BinaryOperator::Multiply);
                assert!(matches!(*right, Expr::Literal(100.0)));
                match *left {
                    Expr::BinaryOp {
                        op: op2,
                        left: left2,
                        right: right2,
                    } => {
                        assert_eq!(op2, BinaryOperator::Divide);
                        assert!(matches!(*left2, Expr::ConceptRef(_)));
                        assert!(matches!(*right2, Expr::ConceptRef(_)));
                    }
                    _ => panic!("Expected BinaryOp for left side"),
                }
            }
            _ => panic!("Expected BinaryOp"),
        }
    }

    #[test]
    fn test_parse_concept_ref_missing_unit() {
        let result = FormulaParser::parse("#us-gaap.Revenue");
        assert!(matches!(result, Err(FormulaError::Syntax { .. })));
    }

    #[test]
    fn test_parse_concept_ref_invalid_unit() {
        let result = FormulaParser::parse("#us-gaap.Revenue[invalid_unit]");
        assert!(matches!(result, Err(FormulaError::Syntax { .. })));
    }

    #[test]
    fn test_parse_concept_ref_missing_bracket() {
        let result = FormulaParser::parse("#us-gaap.Revenue[currency");
        assert!(matches!(result, Err(FormulaError::Syntax { .. })));
    }

    #[test]
    fn test_parse_concept_ref_missing_dot() {
        let result = FormulaParser::parse("#us-gaapRevenue[currency]");
        assert!(matches!(result, Err(FormulaError::Syntax { .. })));
    }
}
