use std::cmp::Ordering;
use std::fmt;
use std::num::ParseIntError;
use std::str::FromStr;

use super::shard::{RegionId, ShardId};

/// Globally unique identifier for a WAL segment.
///
/// Ordering: by (`region_id`, `shard_id`, seqno) -- allows total ordering
/// across all segments in the cluster.
///
/// Display: `"r0:s0:42"` -- human-readable for logs and tidalctl output.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct WalSegmentId {
    pub region_id: RegionId,
    pub shard_id: ShardId,
    pub seqno: u64,
}

impl WalSegmentId {
    #[must_use]
    pub const fn new(region_id: RegionId, shard_id: ShardId, seqno: u64) -> Self {
        Self {
            region_id,
            shard_id,
            seqno,
        }
    }

    /// Create a segment ID for the default single-node deployment.
    #[must_use]
    pub const fn single_node(seqno: u64) -> Self {
        Self::new(RegionId::SINGLE, ShardId::SINGLE, seqno)
    }
}

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

impl Ord for WalSegmentId {
    fn cmp(&self, other: &Self) -> Ordering {
        self.region_id
            .cmp(&other.region_id)
            .then(self.shard_id.cmp(&other.shard_id))
            .then(self.seqno.cmp(&other.seqno))
    }
}

impl fmt::Display for WalSegmentId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}:{}", self.region_id, self.shard_id, self.seqno)
    }
}

/// Error returned when parsing a `WalSegmentId` from a string fails.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseSegmentIdError {
    /// Expected exactly 3 colon-separated parts (`"r0:s0:42"`).
    WrongPartCount,
    /// Region part must start with `'r'`.
    MissingRegionPrefix,
    /// Shard part must start with `'s'`.
    MissingShardPrefix,
    /// A numeric field could not be parsed.
    InvalidNumber(ParseIntError),
}

impl fmt::Display for ParseSegmentIdError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::WrongPartCount => write!(f, "expected format \"r<region>:s<shard>:<seqno>\""),
            Self::MissingRegionPrefix => write!(f, "region part must start with 'r'"),
            Self::MissingShardPrefix => write!(f, "shard part must start with 's'"),
            Self::InvalidNumber(e) => write!(f, "invalid number: {e}"),
        }
    }
}

impl std::error::Error for ParseSegmentIdError {}

impl From<ParseIntError> for ParseSegmentIdError {
    fn from(e: ParseIntError) -> Self {
        Self::InvalidNumber(e)
    }
}

impl FromStr for WalSegmentId {
    type Err = ParseSegmentIdError;

    /// Parse a segment ID from `"r<region>:s<shard>:<seqno>"` format.
    ///
    /// # Examples
    ///
    /// ```
    /// use tidaldb::replication::segment_id::WalSegmentId;
    /// let id: WalSegmentId = "r0:s0:42".parse().unwrap();
    /// assert_eq!(id.seqno, 42);
    /// ```
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(':').collect();
        if parts.len() != 3 {
            return Err(ParseSegmentIdError::WrongPartCount);
        }
        let region_str = parts[0]
            .strip_prefix('r')
            .ok_or(ParseSegmentIdError::MissingRegionPrefix)?;
        let shard_str = parts[1]
            .strip_prefix('s')
            .ok_or(ParseSegmentIdError::MissingShardPrefix)?;
        let region_id = RegionId(region_str.parse::<u16>()?);
        let shard_id = ShardId(shard_str.parse::<u16>()?);
        let seqno = parts[2].parse::<u64>()?;
        Ok(Self {
            region_id,
            shard_id,
            seqno,
        })
    }
}

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

    #[test]
    fn segment_id_ordering() {
        let a = WalSegmentId::new(RegionId(0), ShardId(0), 1);
        let b = WalSegmentId::new(RegionId(0), ShardId(0), 2);
        let c = WalSegmentId::new(RegionId(0), ShardId(1), 0);
        let d = WalSegmentId::new(RegionId(1), ShardId(0), 0);
        assert!(a < b);
        assert!(b < c);
        assert!(c < d);
    }

    #[test]
    fn segment_id_display() {
        let id = WalSegmentId::new(RegionId(2), ShardId(3), 42);
        assert_eq!(id.to_string(), "r2:s3:42");
    }

    #[test]
    fn single_node_defaults() {
        assert_eq!(ShardId::SINGLE, ShardId(0));
        assert_eq!(RegionId::SINGLE, RegionId(0));
        assert_eq!(WalSegmentId::single_node(99).to_string(), "r0:s0:99");
    }

    #[test]
    fn single_node_ordering_by_seqno() {
        let a = WalSegmentId::single_node(10);
        let b = WalSegmentId::single_node(20);
        assert!(a < b);
    }

    #[test]
    fn parse_roundtrip() {
        let id = WalSegmentId::new(RegionId(2), ShardId(3), 42);
        let s = id.to_string();
        let parsed: WalSegmentId = s.parse().expect("should parse");
        assert_eq!(parsed, id);
    }

    #[test]
    fn parse_single_node() {
        let parsed: WalSegmentId = "r0:s0:42".parse().expect("should parse");
        assert_eq!(parsed, WalSegmentId::single_node(42));
    }

    #[test]
    fn parse_wrong_format_errors() {
        assert!("bad".parse::<WalSegmentId>().is_err());
        assert!("r0:s0".parse::<WalSegmentId>().is_err());
        assert!("0:0:42".parse::<WalSegmentId>().is_err());
        assert!("r0:0:42".parse::<WalSegmentId>().is_err());
        assert!("r0:s0:abc".parse::<WalSegmentId>().is_err());
    }
}
