use std::{
    cmp::Ordering,
    fmt::{Display, Formatter, Result as FmtResult},
    str::FromStr,
};

use serde::{Serialize, Serializer};

use crate::error::ParseIdError;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Segment {
    Number(u64),
    Letters(String),
}

impl Ord for Segment {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (Self::Number(left), Self::Number(right)) => left.cmp(right),
            (Self::Letters(left), Self::Letters(right)) => left.cmp(right),
            // If ID is formed properly, Letter and Number should never compare
            (Self::Number(_), Self::Letters(_)) => Ordering::Less,
            (Self::Letters(_), Self::Number(_)) => Ordering::Greater,
        }
    }
}

impl PartialOrd for Segment {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Display for Segment {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        match self {
            Segment::Number(value) => write!(formatter, "{value}"),
            Segment::Letters(value) => formatter.write_str(value),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FolgezettelId {
    segments: Vec<Segment>,
}

impl FolgezettelId {
    pub fn parse(input: impl AsRef<str>) -> Result<Self, ParseIdError> {
        let input = input.as_ref();

        if input.is_empty() {
            return Err(ParseIdError::Empty);
        }

        let mut segments = Vec::new();
        let mut position = 0;

        while position < input.len() {
            let rest = &input[position..];
            // First position is always number, odd = number even = letter
            let segment = if segments.len() % 2 == 0 {
                let digits = leading_run(rest, |c| c.is_ascii_digit());

                if digits.is_empty() {
                    return Err(ParseIdError::ExpectedNumber { position });
                }

                let number = digits
                    .parse()
                    .map_err(|_| ParseIdError::NumberOverflow { position })?;
                position += digits.len();
                Segment::Number(number)
            } else {
                let letters = leading_run(rest, |c| c.is_ascii_alphabetic());
                if letters.is_empty() {
                    return Err(ParseIdError::ExpectedLetter { position });
                }
                position += letters.len();
                Segment::Letters(letters.to_ascii_lowercase())
            };

            segments.push(segment);
        }
        Ok(Self { segments })
    }

    pub fn segments(&self) -> &[Segment] {
        &self.segments
    }

    pub fn parent(&self) -> Option<Self> {
        if self.segments.len() <= 1 {
            return None;
        }

        Some(Self {
            segments: self.segments[..self.segments.len() - 1].to_vec(),
        })
    }
}

fn leading_run(input: &str, matches: impl Fn(char) -> bool) -> &str {
    let end = input
        .find(|character| !matches(character))
        .unwrap_or(input.len());
    &input[..end]
}

impl FromStr for FolgezettelId {
    type Err = ParseIdError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        Self::parse(input)
    }
}

impl From<&FolgezettelId> for String {
    fn from(id: &FolgezettelId) -> Self {
        id.to_string()
    }
}

impl Display for FolgezettelId {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        for segment in &self.segments {
            write!(formatter, "{segment}")?;
        }
        Ok(())
    }
}

impl Serialize for FolgezettelId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_str(self)
    }
}

#[test]
fn reject_malformed_ids() {
    assert_eq!(FolgezettelId::parse("").unwrap_err(), ParseIdError::Empty);
    assert_eq!(
        FolgezettelId::parse("a1").unwrap_err(),
        ParseIdError::ExpectedNumber { position: 0 }
    );
    assert_eq!(
        FolgezettelId::parse("1$").unwrap_err(),
        ParseIdError::ExpectedLetter { position: 1 }
    );
    assert_eq!(
        FolgezettelId::parse("99999999999999999999").unwrap_err(),
        ParseIdError::NumberOverflow { position: 0 }
    );
}

#[test]
fn normalizes_uppercase_letters() {
    assert_eq!(
        FolgezettelId::parse("1ABCD").unwrap(),
        FolgezettelId::parse("1abcd").unwrap()
    );
}
