use std::{borrow::Cow, ops::Range};

use crate::{
    ParseError, Parser, ParserErrorKind,
    ast::{Ident, Path, StructLiteralField},
    lexer::Token,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOpKind {
    // -- comparisons --
    Eq,
    Neq,
    Gt,
    Lt,
    Gte,
    Lte,

    // -- conjunctions --
    And,
    Or,
}

/// A field binding inside an `is` struct payload: `{ name: pattern }`.
/// The `pattern` is a bare `Ident` for a wildcard or any other `Expr` for a concrete match.
#[derive(Debug)]
pub struct IsPayloadField<'a> {
    pub name: Ident<'a>,
    pub pattern: Expr<'a>,
}

/// The payload portion of a variant destructor in an `is` expression.
#[derive(Debug)]
pub enum IsPayload<'a> {
    /// No payload — unit variant: `status is main.Status.Published`
    Unit,
    /// Tuple payload: `id is main.Option.Some(x)` or `id is main.Option.Some(5)`
    Tuple(Vec<Expr<'a>>),
    /// Struct payload: `shape is main.Shape.Circle { radius: r }`
    Struct(Vec<IsPayloadField<'a>>),
}

/// The RHS of an `is` expression — a variant path plus optional destructor payload.
#[derive(Debug)]
pub struct IsPattern<'a> {
    /// The full path to the variant, e.g. `main.Option.Some`.
    pub variant: Path<'a>,
    pub payload: IsPayload<'a>,
}

#[derive(Debug)]
pub enum ExprKind<'a> {
    IntegerLiteral(Cow<'a, str>),
    StringLiteral(Cow<'a, str>),
    Bool(bool),
    Path(Path<'a>),
    TupleLiteral(Vec<Expr<'a>>),
    StructLiteral(Vec<StructLiteralField<'a>>),
    BinaryOp {
        left: Box<Expr<'a>>,
        op: BinaryOpKind,
        right: Box<Expr<'a>>,
    },
    /// Pattern match: `expr is VariantPath` or `expr is VariantPath(payload...)`.
    Is {
        left: Box<Expr<'a>>,
        pattern: IsPattern<'a>,
    },
    /// Enum constructor: `db.Type.Variant(arg, ...)` used in insert values.
    EnumConstructor {
        path: Path<'a>,
        args: Vec<Expr<'a>>,
    },
}

#[derive(Debug)]
pub struct Expr<'a> {
    pub span: Range<usize>,
    pub kind: ExprKind<'a>,
}

impl<'a> Parser<'a> {
    pub(crate) fn is_expression_start(token: &Token<'_>) -> bool {
        matches!(
            token,
            Token::IntegerLiteral(_)
                | Token::StringLiteral(_)
                | Token::Identifier(_)
                | Token::True
                | Token::False
                | Token::LParen
                | Token::LBrace
        )
    }

    fn parse_primary_expr(&mut self) -> Result<Expr<'a>, ParseError> {
        match self.lexer.peek().token {
            Token::IntegerLiteral(_) => {
                let tok = self.lexer.next();
                let Token::IntegerLiteral(s) = &tok.token else {
                    unreachable!()
                };
                Ok(Expr {
                    span: tok.span.clone(),
                    kind: ExprKind::IntegerLiteral(s.clone()),
                })
            }
            Token::StringLiteral(_) => {
                let tok = self.lexer.next();
                let Token::StringLiteral(s) = &tok.token else {
                    unreachable!()
                };
                Ok(Expr {
                    span: tok.span.clone(),
                    kind: ExprKind::StringLiteral(s.clone()),
                })
            }
            Token::Identifier(_) => {
                let path = self.parse_path()?;
                if self.lexer.peek().token == Token::LParen {
                    self.lexer.advance();
                    let mut args = Vec::new();
                    while self.lexer.peek().token != Token::RParen {
                        if !args.is_empty() {
                            self.consume(&[Token::Comma])?;
                        }
                        args.push(self.parse_primary_expr()?);
                    }
                    let span_end = self.consume(&[Token::RParen])?.span.end;
                    return Ok(Expr {
                        span: path.span.start..span_end,
                        kind: ExprKind::EnumConstructor { path, args },
                    });
                }
                Ok(Expr {
                    span: path.span.clone(),
                    kind: ExprKind::Path(path),
                })
            }
            Token::True => {
                let tok = self.lexer.next();
                let span = tok.span.clone();
                Ok(Expr {
                    span,
                    kind: ExprKind::Bool(true),
                })
            }
            Token::False => {
                let tok = self.lexer.next();
                let span = tok.span.clone();
                Ok(Expr {
                    span,
                    kind: ExprKind::Bool(false),
                })
            }
            Token::LParen => {
                let span_start = self.lexer.next().span.start;
                let exprs = self.parse_tuple_literal_exprs()?;
                let span_end = self.consume(&[Token::RParen])?.span.end;
                Ok(Expr {
                    span: span_start..span_end,
                    kind: ExprKind::TupleLiteral(exprs),
                })
            }
            Token::LBrace => {
                let span_start = self.lexer.next().span.start;
                let fields = self.parse_struct_literal_fields()?;
                let span_end = self.consume(&[Token::RBrace])?.span.end;
                Ok(Expr {
                    span: span_start..span_end,
                    kind: ExprKind::StructLiteral(fields),
                })
            }
            _ => {
                let tok = self.lexer.next();
                Err(ParseError {
                    span: tok.span.clone(),
                    kind: ParserErrorKind::unexpected_token(
                        &[
                            Token::empty_string(),
                            Token::empty_integer(),
                            Token::empty_ident(),
                            Token::True,
                            Token::False,
                        ],
                        &tok.token,
                    ),
                })
            }
        }
    }

    fn parse_comparison(&mut self) -> Result<Expr<'a>, ParseError> {
        let lhs = self.parse_primary_expr()?;

        let tok = self.lexer.peek();

        if tok.token == Token::Is {
            self.lexer.advance();
            let variant = self.parse_path()?;
            let payload = match self.lexer.peek().token {
                Token::LParen => {
                    self.lexer.advance();
                    let mut args = Vec::new();
                    while self.lexer.peek().token != Token::RParen {
                        if !args.is_empty() {
                            self.consume(&[Token::Comma])?;
                        }
                        args.push(self.parse_primary_expr()?);
                    }
                    self.consume(&[Token::RParen])?;
                    IsPayload::Tuple(args)
                }
                Token::LBrace => {
                    self.lexer.advance();
                    let mut fields = Vec::new();
                    while self.lexer.peek().token != Token::RBrace {
                        if !fields.is_empty() {
                            self.consume(&[Token::Comma])?;
                        }
                        let name = self.parse_ident()?;
                        self.consume(&[Token::Colon])?;
                        let pattern = self.parse_primary_expr()?;
                        fields.push(IsPayloadField { name, pattern });
                    }
                    self.consume(&[Token::RBrace])?;
                    IsPayload::Struct(fields)
                }
                _ => IsPayload::Unit,
            };
            let span_end = match &payload {
                IsPayload::Unit => variant.span.end,
                IsPayload::Tuple(args) => args.last().map(|e| e.span.end).unwrap_or(variant.span.end),
                IsPayload::Struct(fields) => fields.last().map(|f| f.pattern.span.end).unwrap_or(variant.span.end),
            };
            return Ok(Expr {
                span: lhs.span.start..span_end,
                kind: ExprKind::Is {
                    left: Box::new(lhs),
                    pattern: IsPattern { variant, payload },
                },
            });
        }

        let op = match tok.token {
            Token::Eq => BinaryOpKind::Eq,
            Token::Neq => BinaryOpKind::Neq,
            Token::Lt => BinaryOpKind::Lt,
            Token::Gt => BinaryOpKind::Gt,
            Token::Gte => BinaryOpKind::Gte,
            Token::Lte => BinaryOpKind::Lte,
            Token::And => BinaryOpKind::And,
            Token::Or => BinaryOpKind::Or,
            _ => return Ok(lhs),
        };
        self.lexer.advance();

        let rhs = self.parse_primary_expr()?;

        Ok(Expr {
            span: lhs.span.start..rhs.span.end,
            kind: ExprKind::BinaryOp {
                left: Box::new(lhs),
                op,
                right: Box::new(rhs),
            },
        })
    }

    fn parse_and(&mut self) -> Result<Expr<'a>, ParseError> {
        let mut lhs = self.parse_comparison()?;
        while self.lexer.peek().token == Token::And {
            self.lexer.advance();
            let rhs = self.parse_comparison()?;
            let span = lhs.span.start..rhs.span.end;
            lhs = Expr {
                span,
                kind: ExprKind::BinaryOp {
                    left: Box::new(lhs),
                    op: BinaryOpKind::And,
                    right: Box::new(rhs),
                },
            }
        }
        Ok(lhs)
    }

    fn parse_or(&mut self) -> Result<Expr<'a>, ParseError> {
        let mut lhs = self.parse_and()?;
        while self.lexer.peek().token == Token::Or {
            self.lexer.advance();
            let rhs = self.parse_and()?;
            let span = lhs.span.start..rhs.span.end;
            lhs = Expr {
                span,
                kind: ExprKind::BinaryOp {
                    left: Box::new(lhs),
                    op: BinaryOpKind::Or,
                    right: Box::new(rhs),
                },
            }
        }
        Ok(lhs)
    }

    pub(crate) fn parse_expr(&mut self) -> Result<Expr<'a>, ParseError> {
        self.parse_or()
    }
}

#[cfg(test)]
mod tests {
    use crate::{ast::*, parse, tests::assert_no_errors};

    fn named_insert(source: &str) -> Vec<StructLiteralField<'_>> {
        let (stmts, errors) = parse(source);
        assert_no_errors(&errors);
        let Stmt::InsertInto(stmt) = stmts.into_iter().next().unwrap() else {
            panic!("expected insert")
        };
        let InsertValues::Named(fields) = stmt.values else {
            panic!("expected named values")
        };
        fields
    }

    fn unnamed_insert(source: &str) -> Vec<Expr<'_>> {
        let (stmts, errors) = parse(source);
        assert_no_errors(&errors);
        let Stmt::InsertInto(stmt) = stmts.into_iter().next().unwrap() else {
            panic!("expected insert")
        };
        let InsertValues::Unnamed(exprs) = stmt.values else {
            panic!("expected unnamed values")
        };
        exprs
    }

    #[test]
    fn test_struct_literal_with_tuple_field() {
        let fields = named_insert("insert into foo { point: (1, 2) };");
        assert_eq!(fields.len(), 1);
        assert_eq!(fields[0].column.name, "point".into());
        let ExprKind::TupleLiteral(elems) = &fields[0].value.kind else {
            panic!("expected tuple literal")
        };
        assert_eq!(elems.len(), 2);
        assert!(matches!(elems[0].kind, ExprKind::IntegerLiteral(ref s) if s == "1"));
        assert!(matches!(elems[1].kind, ExprKind::IntegerLiteral(ref s) if s == "2"));
    }

    #[test]
    fn test_struct_literal_with_nested_struct_field() {
        let fields = named_insert(r#"insert into foo { address: { city: "NYC", zip: 10001 } };"#);
        assert_eq!(fields.len(), 1);
        assert_eq!(fields[0].column.name, "address".into());
        let ExprKind::StructLiteral(inner) = &fields[0].value.kind else {
            panic!("expected struct literal")
        };
        assert_eq!(inner.len(), 2);
        assert_eq!(inner[0].column.name, "city".into());
        assert!(matches!(inner[0].value.kind, ExprKind::StringLiteral(ref s) if s == "NYC"));
        assert_eq!(inner[1].column.name, "zip".into());
        assert!(matches!(inner[1].value.kind, ExprKind::IntegerLiteral(ref s) if s == "10001"));
    }

    #[test]
    fn test_tuple_literal_containing_structs() {
        let exprs = unnamed_insert("insert into foo ({ x: 1, y: 2 }, { x: 3, y: 4 });");
        assert_eq!(exprs.len(), 2);
        for (expr, (ex, ey)) in exprs.iter().zip([(1, 2), (3, 4)]) {
            let ExprKind::StructLiteral(fields) = &expr.kind else {
                panic!("expected struct literal")
            };
            assert_eq!(fields.len(), 2);
            assert!(
                matches!(fields[0].value.kind, ExprKind::IntegerLiteral(ref s) if s.parse::<i64>().unwrap() == ex)
            );
            assert!(
                matches!(fields[1].value.kind, ExprKind::IntegerLiteral(ref s) if s.parse::<i64>().unwrap() == ey)
            );
        }
    }

    #[test]
    fn test_deeply_nested_struct() {
        let fields = named_insert("insert into foo { a: { b: { c: 42 } } };");
        assert_eq!(fields.len(), 1);
        let ExprKind::StructLiteral(b_fields) = &fields[0].value.kind else {
            panic!("expected struct literal for a")
        };
        assert_eq!(b_fields[0].column.name, "b".into());
        let ExprKind::StructLiteral(c_fields) = &b_fields[0].value.kind else {
            panic!("expected struct literal for b")
        };
        assert_eq!(c_fields[0].column.name, "c".into());
        assert!(matches!(c_fields[0].value.kind, ExprKind::IntegerLiteral(ref s) if s == "42"));
    }

    #[test]
    fn test_tuple_of_tuples() {
        let exprs = unnamed_insert("insert into foo ((1, 2), (3, 4));");
        assert_eq!(exprs.len(), 2);
        for (expr, (ea, eb)) in exprs.iter().zip([(1, 2), (3, 4)]) {
            let ExprKind::TupleLiteral(inner) = &expr.kind else {
                panic!("expected tuple literal")
            };
            assert_eq!(inner.len(), 2);
            assert!(
                matches!(inner[0].kind, ExprKind::IntegerLiteral(ref s) if s.parse::<i64>().unwrap() == ea)
            );
            assert!(
                matches!(inner[1].kind, ExprKind::IntegerLiteral(ref s) if s.parse::<i64>().unwrap() == eb)
            );
        }
    }
}
