//! atproto-static-web   A simple web viewer for AT Proto.
//! Copyright (C) 2025  AverageHelper
//!
//! This program is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by
//! the Free Software Foundation, either version 3 of the License, or
//! (at your option) any later version.
//!
//! This program is distributed in the hope that it will be useful,
//! but WITHOUT ANY WARRANTY; without even the implied warranty of
//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//! GNU General Public License for more details.
//!
//! You should have received a copy of the GNU General Public License
//! along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::atproto::{Handle, handle::HandleParseError};
use ascii::AsAsciiStr;
use std::str::FromStr;

/// See https://atproto.com/specs/nsid
#[derive(Debug, PartialEq, Eq)]
pub struct NamespacedIdentifier(ascii::AsciiString);

impl FromStr for NamespacedIdentifier {
	type Err = NsidParseError;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		// NSID Syntax: https://atproto.com/specs/nsid#nsid-syntax

		// Spec: "must contain only ASCII characters"
		// Handled by parsing the name and domain authority separately and checking for ASCII

		// Spec: "can have a maximum total length of 317 characters"
		if s.len() > 317 {
			return Err(NsidParseError::TooLong);
		}

		// Spec: "A fragment may be appended to an NSID in some contexts to refer to a specific sub-field within the schema. The fragment is separated from the NSID by an ASCII hash character (`#``)."
		let mut fragment_split = s.split('#');
		let s = fragment_split.next().expect("nonempty");

		let fragment = match fragment_split.next() {
			None => None,
			Some(f) => {
				// Spec: "The fragment identifier string (after the `#`) has the same syntax restrictions as the final segment of an NSID: ASCII alphabetic, one or more characters, length restricted, etc."
				// So, all the same restrictions as Name below:
				let fragment = f.as_ascii_str()?;
				if fragment.is_empty() {
					return Err(NsidParseError::FragmentEmpty);
				} else if fragment.len() > 63 {
					return Err(NsidParseError::FragmentTooLong);
				}

				if fragment.first().expect("nonempty").is_ascii_digit() {
					return Err(NsidParseError::FragmentStartsWithDigit);
				}

				// TODO: Spec: "Name: the allowed characters are ASCII letters and digits only (A-Z, a-z, 0-9); hyphens are not allowed"

				Some(fragment)
			}
		};

		// Spec: "separate the domain authority and the name by an ASCII period character (`.`)"
		// let mut segments: Vec<_> = s.split(ascii::AsciiChar::Dot).collect();
		let mut segments: Vec<_> = s.split('.').collect();

		// Spec: "must have at least 3 segments"
		if segments.len() < 3 {
			return Err(NsidParseError::NotEnoughSegments);
		}

		// Spec: "[The domain authority] is followed by a name segment which must be an ASCII camel-case string."
		let name = segments.pop().expect("nonempty").as_ascii_str()?;

		// Spec: "Name: must have at least 1 and at most 63 characters"
		if name.is_empty() {
			return Err(NsidParseError::NameEmpty);
		} else if name.len() > 63 {
			return Err(NsidParseError::NameTooLong);
		}

		// Spec: "Name: the first character can not be a digit"
		if name.first().expect("nonempty").is_ascii_digit() {
			return Err(NsidParseError::NameStartsWithDigit);
		}

		// TODO: Spec: "Name: the allowed characters are ASCII letters and digits only (A-Z, a-z, 0-9); hyphens are not allowed"

		// Spec: "The domain authority part of an NSID must be a valid handle with the order of segments reversed."
		segments.reverse();
		let domain_authority = segments
			.join(".")
			.parse::<Handle>()? // ensures ASCII
			.segments()
			.rev()
			.map(|s| s.as_str())
			.collect::<Vec<_>>()
			.join(".");
		let domain_authority = domain_authority.as_ascii_str().expect("ascii");

		let mut value = format!("{domain_authority}.{name}")
			.as_ascii_str()
			.expect("ascii")
			.to_owned();
		if let Some(fragment) = fragment {
			value.push(ascii::AsciiChar::Hash);
			value.push_str(fragment);
		}

		Ok(Self(value))
	}
}

impl core::fmt::Display for NamespacedIdentifier {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", self.0)
	}
}

impl NamespacedIdentifier {
	pub fn from_ascii_str(s: &ascii::AsciiStr) -> Result<Self, NsidParseError> {
		Self::from_str(s.into())
	}

	pub fn fragment(&self) -> Option<&ascii::AsciiStr> {
		let mut parts = self.0.split(ascii::AsciiChar::Hash);
		parts.next(); // main portion
		parts.next() // fragment
	}

	pub fn app_bsky_feed_post() -> Self {
		Self("app.bsky.feed.post".as_ascii_str().unwrap().to_owned())
	}
}

#[derive(Debug)]
pub enum NsidParseError {
	/// The fragment is empty.
	FragmentEmpty,

	/// The fragment strts with a digit.
	FragmentStartsWithDigit,

	/// The fragment is more than 63 characters.
	FragmentTooLong,

	/// The domain authority is not a valid reversed Handle.
	InvalidDomainAuthority(HandleParseError),

	/// The Name segment is empty.
	NameEmpty,

	/// The Name segment starts with a digit.
	NameStartsWithDigit,

	/// The Name segment is more than 63 characters.
	NameTooLong,

	/// Value contains non-ASCII characters.
	NotAscii,

	/// Value contains fewer than 3 segments.
	NotEnoughSegments,

	/// Value is more than 317 characters in length.
	TooLong,
}

impl core::fmt::Display for NsidParseError {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::FragmentEmpty => write!(f, "NSID fragment is empty"),
			Self::FragmentStartsWithDigit => write!(f, "NSID fragment starts with a digit"),
			Self::FragmentTooLong => {
				write!(f, "NSID fragment is more than 63 characters in length")
			}
			Self::InvalidDomainAuthority(err) => {
				write!(f, "NSID value contains an invalid domain authority: {err}")
			}
			Self::NameEmpty => write!(f, "NSID value's Name segment is empty"),
			Self::NameStartsWithDigit => write!(f, "NSID value's Name segment starts with a digit"),
			Self::NameTooLong => write!(
				f,
				"NSID value's Name segment more than 63 characters in length"
			),
			Self::NotAscii => write!(f, "NSID value contains non-ASCII characters"),
			Self::NotEnoughSegments => write!(f, "NSID value contains fewer than 3 segments"),
			Self::TooLong => write!(f, "NSID value is more than 317 characters in length"),
		}
	}
}

impl core::error::Error for NsidParseError {}

impl From<HandleParseError> for NsidParseError {
	fn from(value: HandleParseError) -> Self {
		Self::InvalidDomainAuthority(value)
	}
}

impl From<ascii::AsAsciiStrError> for NsidParseError {
	fn from(_: ascii::AsAsciiStrError) -> Self {
		Self::NotAscii
	}
}

// MARK: - Tests

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

	#[test]
	fn test_parsing() {
		// See https://atproto.com/specs/nsid#examples
		// Spec: "Syntactically valid NSIDs:"
		let cases = [
			"com.example.fooBar",
			"net.users.bob.ping",
			"a-0.b-1.c",
			"a.b.c",
			"com.example.fooBarV2",
			"cn.8.lex.stuff",
		];
		for case in cases {
			let result = case.parse::<NamespacedIdentifier>().unwrap();
			assert_eq!(result.to_string(), case);
		}
	}

	#[test]
	fn test_parse_syntax_failure() {
		// See https://atproto.com/specs/nsid#examples
		// Spec: "Invalid NSIDs:"
		let cases = ["com.exa💩ple.thing", "com.example", "com.example.3"];
		for case in cases {
			let result = case.parse::<NamespacedIdentifier>();
			assert!(result.is_err()); // TODO: Assert specific errors
		}
	}
}
