use std::ops::Range;

use itertools::Itertools;
use tempest_core::tempest_str::TempestStr;

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

#[derive(Debug)]
pub enum PathSegment<'a> {
    Named(Ident<'a>),
    Indexed(u32),
}

impl std::fmt::Display for PathSegment<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PathSegment::Named(ident) => write!(f, "{}", ident.name),
            PathSegment::Indexed(idx) => write!(f, "{}", idx),
        }
    }
}

/// A dot-separated path of named and indexed segments.
///
/// Covers column refs (`id`, `user.address.city`, `row.coords.0`),
/// type/table references (`main.users`, `Option`), and enum variant
/// paths (`Option.None`). Resolution of what a path refers to is
/// deferred to the planner/compiler.
#[derive(Debug)]
pub struct Path<'a> {
    pub span: Range<usize>,
    pub segments: Vec<PathSegment<'a>>,
}

impl<'a> Path<'a> {
    /// The last named segment — the "local name" of the path.
    /// For `main.users` returns `users`, for `row.coords.0` returns `coords`.
    pub fn name(&self) -> Option<&Ident<'a>> {
        self.segments.iter().rev().find_map(|s| match s {
            PathSegment::Named(i) => Some(i),
            PathSegment::Indexed(_) => None,
        })
    }

    /// The first named segment when there are at least two named segments.
    /// For `main.users` returns `main`, for bare `id` returns `None`.
    pub fn database(&self) -> Option<&Ident<'a>> {
        let named: Vec<_> = self
            .segments
            .iter()
            .filter_map(|s| match s {
                PathSegment::Named(i) => Some(i),
                PathSegment::Indexed(_) => None,
            })
            .collect();
        if named.len() >= 2 { Some(named[0]) } else { None }
    }

    pub fn render(&self) -> TempestStr<'a> {
        if self.segments.len() == 1 {
            if let PathSegment::Named(ident) = &self.segments[0] {
                return ident.name.clone();
            }
        }
        TempestStr::from_owned(self.segments.iter().join("."))
            .expect("path segment does not contain null bytes")
    }

    #[cfg(any(test, feature = "testing"))]
    pub fn for_testing(database: Option<TempestStr<'a>>, name: TempestStr<'a>) -> Self {
        let mut segments = Vec::new();
        if let Some(db) = database {
            segments.push(PathSegment::Named(Ident::for_testing(db)));
        }
        segments.push(PathSegment::Named(Ident::for_testing(name)));
        Self { span: 0..0, segments }
    }
}

impl std::fmt::Display for Path<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.segments.iter().join("."))
    }
}

impl<'a> Parser<'a> {
    pub(crate) fn parse_tuple_index(&mut self) -> Result<u32, ParseError> {
        let tok = self.lexer.next();
        self.current_span.end = tok.span.end;
        let lit = match &tok.token {
            Token::IntegerLiteral(lit) => lit,
            _ => {
                return Err(ParseError {
                    span: tok.span.clone(),
                    kind: ParserErrorKind::unexpected_token(&[Token::empty_integer()], &tok.token),
                });
            }
        };

        let raw_idx: i64 = lit.parse().expect("lexer guarantees valid integer");
        match raw_idx {
            n if n < 0 => Err(ParseError {
                span: tok.span.clone(),
                kind: ParserErrorKind::NegativeTupleIndex,
            }),
            n => n.try_into().map_err(|_| ParseError {
                span: tok.span.clone(),
                kind: ParserErrorKind::TupleIndexTooLarge,
            }),
        }
    }

    #[instrument(skip_all, level = "trace")]
    pub(crate) fn parse_path(&mut self) -> Result<Path<'a>, ParseError> {
        let tok = self.lexer.peek();
        let (first_segment, span_start) = match &tok.token {
            Token::Identifier(_) => {
                let ident = self.parse_ident()?;
                let start = ident.span.start;
                (PathSegment::Named(ident), start)
            }
            Token::IntegerLiteral(_) => {
                let start = tok.span.start;
                let idx = self.parse_tuple_index()?;
                (PathSegment::Indexed(idx), start)
            }
            _ => {
                let tok = self.lexer.next();
                return Err(ParseError {
                    span: tok.span.clone(),
                    kind: ParserErrorKind::unexpected_token(
                        &[Token::empty_ident(), Token::empty_integer()],
                        &tok.token,
                    ),
                });
            }
        };
        let mut segments = vec![first_segment];

        loop {
            if self.lexer.peek().token != Token::Dot {
                break;
            }
            self.lexer.advance();

            match &self.lexer.peek().token {
                Token::Identifier(_) => {
                    let ident = self.parse_ident()?;
                    segments.push(PathSegment::Named(ident));
                }
                Token::IntegerLiteral(_) => {
                    let idx = self.parse_tuple_index()?;
                    segments.push(PathSegment::Indexed(idx));
                }
                _ => {
                    let tok = self.lexer.next();
                    return Err(ParseError {
                        span: tok.span.clone(),
                        kind: ParserErrorKind::unexpected_token(
                            &[Token::empty_ident(), Token::empty_integer()],
                            &tok.token,
                        ),
                    });
                }
            }
        }

        let span_end = self.current_span.end;
        Ok(Path { span: span_start..span_end, segments })
    }
}

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

    fn parse_path(source: &str) -> Path<'_> {
        let mut parser = Parser::new(source);
        let path = parser.parse_path().expect("parse_path failed");
        parser.assert_finished();
        path
    }

    #[test]
    fn test_bare() {
        let p = parse_path("id");
        assert_eq!(p.segments.len(), 1);
        assert!(matches!(&p.segments[0], PathSegment::Named(i) if i.name == "id".into()));
    }

    #[test]
    fn test_one_named() {
        let p = parse_path("p.id");
        assert_eq!(p.segments.len(), 2);
        assert!(matches!(&p.segments[0], PathSegment::Named(i) if i.name == "p".into()));
        assert!(matches!(&p.segments[1], PathSegment::Named(i) if i.name == "id".into()));
    }

    #[test]
    fn test_one_indexed() {
        let p = parse_path("p.0");
        assert_eq!(p.segments.len(), 2);
        assert!(matches!(&p.segments[0], PathSegment::Named(i) if i.name == "p".into()));
        assert!(matches!(p.segments[1], PathSegment::Indexed(0)));
    }

    #[test]
    fn test_two_named() {
        let p = parse_path("user.address.city");
        assert_eq!(p.segments.len(), 3);
        assert!(matches!(&p.segments[0], PathSegment::Named(i) if i.name == "user".into()));
        assert!(matches!(&p.segments[1], PathSegment::Named(i) if i.name == "address".into()));
        assert!(matches!(&p.segments[2], PathSegment::Named(i) if i.name == "city".into()));
    }

    #[test]
    fn test_named_then_indexed() {
        let p = parse_path("row.coords.1");
        assert_eq!(p.segments.len(), 3);
        assert!(matches!(&p.segments[0], PathSegment::Named(i) if i.name == "row".into()));
        assert!(matches!(&p.segments[1], PathSegment::Named(i) if i.name == "coords".into()));
        assert!(matches!(p.segments[2], PathSegment::Indexed(1)));
    }

    #[test]
    fn test_display_bare() {
        assert_eq!(parse_path("id").to_string(), "id");
    }

    #[test]
    fn test_display_one_named() {
        assert_eq!(parse_path("p.id").to_string(), "p.id");
    }

    #[test]
    fn test_display_one_indexed() {
        assert_eq!(parse_path("p.0").to_string(), "p.0");
    }

    #[test]
    fn test_display_deep() {
        assert_eq!(parse_path("row.coords.1").to_string(), "row.coords.1");
    }

    #[test]
    fn test_database_helper_two_segments() {
        let p = parse_path("main.users");
        assert_eq!(p.database().unwrap().name, "main".into());
        assert_eq!(p.name().unwrap().name, "users".into());
    }

    #[test]
    fn test_database_helper_one_segment() {
        let p = parse_path("id");
        assert!(p.database().is_none());
        assert_eq!(p.name().unwrap().name, "id".into());
    }
}
