use std::borrow::Cow;
use std::num::{ParseFloatError, ParseIntError};
use std::str::FromStr;
use std::time::Duration;

use rust_decimal::Decimal;

use crate::syn::error::{SyntaxError, bail, syntax_error};
use crate::syn::lexer::Lexer;
use crate::syn::parser::ParseResult;
use crate::syn::token::{Span, Token, TokenKind, t};
use crate::val::DecimalExt;
use crate::val::duration::{
	SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, SECONDS_PER_WEEK, SECONDS_PER_YEAR,
};

#[derive(Debug, Clone, Copy)]
pub struct ParsedInt {
	sign: bool,
	int: u64,
}

impl ParsedInt {
	pub fn from_number_str(s: &str, span: Span) -> ParseResult<Self> {
		let (sign, number) = match s.strip_prefix("-") {
			Some(x) => (true, x.parse::<u64>().ok().filter(|x| *x <= (i64::MAX as u64) + 1)),
			None => (false, s.parse::<u64>().ok().filter(|x| *x <= (i64::MAX as u64) + 1)),
		};

		let Some(n) = number else {
			bail!(
				"Failed to parse number: number cannot fit within a 64bit signed integer",
				@span
			)
		};

		Ok(ParsedInt {
			sign,
			int: n,
		})
	}

	pub fn into_int(self, span: Span) -> ParseResult<i64> {
		let int = if self.int <= i64::MAX as u64 {
			if self.sign {
				-(self.int as i64)
			} else {
				self.int as i64
			}
		} else if self.sign {
			debug_assert_eq!(self.int, (i64::MAX as u64) + 1);
			i64::MIN
		} else {
			bail!("Failed to parse number: number cannot fit within a 64bit signed integer", @span)
		};
		Ok(int)
	}

	pub fn into_neg_int(self, span: Span) -> ParseResult<i64> {
		let int = if self.int <= i64::MAX as u64 {
			if self.sign {
				self.int as i64
			} else {
				-(self.int as i64)
			}
		} else if !self.sign {
			debug_assert_eq!(self.int, (i64::MAX as u64) + 1);
			i64::MIN
		} else {
			bail!("Failed to parse number: number cannot fit within a 64bit signed integer", @span)
		};
		Ok(int)
	}
}

#[derive(Debug)]
pub enum Numeric {
	Float(f64),
	Integer(ParsedInt),
	Decimal(Decimal),
	Duration(Duration),
}

/// Like numeric but holds of parsing the a number into a specific value.
#[derive(Debug)]
pub enum NumericKind {
	Float,
	Int,
	Decimal,
	Duration(Duration),
}

#[derive(Debug)]
pub enum NumberKind {
	Integer,
	Float,
	Decimal,
}

enum DurationSuffix {
	Nano,
	Micro,
	Milli,
	Second,
	Minute,
	Hour,
	Day,
	Week,
	Year,
}

pub fn prepare_number_str(str: &str) -> Cow<'_, str> {
	if str.contains('_') {
		Cow::Owned(str.chars().filter(|x| *x != '_').collect())
	} else {
		Cow::Borrowed(str)
	}
}

/// Tokens which can start with digits: Number or Duration.
/// Like numeric but holds off on parsing the a number into a specific value.
pub fn numeric_kind(lexer: &mut Lexer, start: Token) -> Result<NumericKind, SyntaxError> {
	match start.kind {
		t!("-") | t!("+") => match number_kind(lexer, start)? {
			NumberKind::Integer => Ok(NumericKind::Int),
			NumberKind::Float => Ok(NumericKind::Float),
			NumberKind::Decimal => Ok(NumericKind::Decimal),
		},
		TokenKind::NaN | TokenKind::Infinity => Ok(NumericKind::Float),
		TokenKind::Digits => match lexer.reader.peek() {
			Some(b'n' | b's' | b'm' | b'h' | b'y' | b'w' | b'u') => {
				duration(lexer, start).map(NumericKind::Duration)
			}
			Some(b'd') => {
				if let Some(b'e') = lexer.reader.peek1() {
					match number_kind(lexer, start)? {
						NumberKind::Integer => Ok(NumericKind::Int),
						NumberKind::Float => Ok(NumericKind::Float),
						NumberKind::Decimal => Ok(NumericKind::Decimal),
					}
				} else {
					duration(lexer, start).map(NumericKind::Duration)
				}
			}
			Some(x) if !x.is_ascii() => duration(lexer, start).map(NumericKind::Duration),
			_ => match number_kind(lexer, start)? {
				NumberKind::Integer => Ok(NumericKind::Int),
				NumberKind::Float => Ok(NumericKind::Float),
				NumberKind::Decimal => Ok(NumericKind::Decimal),
			},
		},
		x => {
			bail!("Unexpected token `{x}`, expected a numeric value, either a duration or number",@start.span)
		}
	}
}

/// Tokens which can start with digits: Number or Duration.
pub fn numeric(lexer: &mut Lexer, start: Token) -> Result<Numeric, SyntaxError> {
	match start.kind {
		t!("-") | t!("+") => number(lexer, start),
		TokenKind::Digits => match lexer.reader.peek() {
			Some(b'n' | b's' | b'm' | b'h' | b'y' | b'w' | b'u') => {
				duration(lexer, start).map(Numeric::Duration)
			}
			Some(b'd') => {
				if lexer.reader.peek1() == Some(b'e') {
					number(lexer, start)
				} else {
					duration(lexer, start).map(Numeric::Duration)
				}
			}
			Some(0xC2) => duration(lexer, start).map(Numeric::Duration),
			_ => number(lexer, start),
		},
		x => {
			bail!("Unexpected token `{x}`, expected a numeric value, either a duration or number",@start.span)
		}
	}
}

pub fn number_kind(lexer: &mut Lexer, start: Token) -> Result<NumberKind, SyntaxError> {
	let offset = start.span.offset;
	match start.kind {
		t!("-") | t!("+") => {
			if eat_infinity(lexer, offset)? {
				return Ok(NumberKind::Float);
			}
			eat_digits1(lexer, offset)?;
		}
		TokenKind::Digits => {}
		TokenKind::Infinity => return Ok(NumberKind::Float),
		TokenKind::NaN => return Ok(NumberKind::Float),
		x => bail!("Unexpected start token for integer: {x}",@start.span),
	}

	let mut kind = NumberKind::Integer;

	let before_mantissa = lexer.reader.offset();
	// need to test for digit.. or digit.foo
	if lexer.reader.peek1().map(|x| x.is_ascii_digit()).unwrap_or(false) && lexer.eat(b'.') {
		eat_digits1(lexer, before_mantissa)?;
		kind = NumberKind::Float;
	}

	let before_exponent = lexer.reader.offset();
	if lexer.eat(b'e') || lexer.eat(b'E') {
		if !lexer.eat(b'-') {
			lexer.eat(b'+');
		}

		eat_digits1(lexer, before_exponent)?;
		kind = NumberKind::Float;
	}

	if !lexer.eat(b'f') {
		if lexer.eat(b'd') {
			lexer.expect('e')?;
			lexer.expect('c')?;
			kind = NumberKind::Decimal;
		}
	} else {
		kind = NumberKind::Float;
	}

	if has_ident_after(lexer) {
		let char = lexer.reader.next().expect("lexer validated input");
		let char = lexer.reader.convert_to_char(char)?;
		bail!("Invalid token, found unexpected character `{char}` after number token", @lexer.current_span())
	}
	Ok(kind)
}

pub fn number(lexer: &mut Lexer, start: Token) -> Result<Numeric, SyntaxError> {
	let kind = number_kind(lexer, start)?;
	let span = lexer.current_span();
	let number_str = prepare_number_str(lexer.span_str(span));
	match kind {
		NumberKind::Integer => {
			Ok(Numeric::Integer(ParsedInt::from_number_str(number_str.as_ref(), span)?))
		}
		NumberKind::Float => {
			// Number strings cannot be empty so checking the first byte is always valid.
			if number_str.as_bytes()[0] == b'N' {
				Ok(Numeric::Float(f64::NAN))
			} else if number_str.as_bytes()[0] == b'-' && number_str.as_bytes()[1] == b'I' {
				// No need to check the whole string, if it starts with '-I' it  has to be negative
				// infinity
				Ok(Numeric::Float(f64::NEG_INFINITY))
			} else if number_str.as_bytes()[0] == b'+' && number_str.as_bytes()[1] == b'I'
				|| number_str.as_bytes()[0] == b'I'
			{
				// No need to check the whole string, if it starts with '+I' or 'I' it  has to be
				// infinity
				Ok(Numeric::Float(f64::INFINITY))
			} else {
				let number_str = number_str.trim_end_matches('f');
				number_str.parse().map(Numeric::Float).map_err(
					|e| syntax_error!("Failed to parse number: {e}", @lexer.current_span()),
				)
			}
		}
		NumberKind::Decimal => {
			let number_str = number_str.trim_end_matches("dec");
			let decimal = if number_str.contains(['e', 'E']) {
				Decimal::from_scientific(number_str).map_err(
					|e| syntax_error!("Failed to parser decimal: {e}", @lexer.current_span()),
				)?
			} else {
				Decimal::from_str_normalized(number_str).map_err(
					|e| syntax_error!("Failed to parser decimal: {e}", @lexer.current_span()),
				)?
			};
			Ok(Numeric::Decimal(decimal))
		}
	}
}

/// Generic integer parsing method,
/// works for all unsigned integers.
pub fn integer<I>(lexer: &mut Lexer, start: Token) -> Result<I, SyntaxError>
where
	I: FromStr<Err = ParseIntError>,
{
	let offset = start.span.offset;
	match start.kind {
		t!("-") | t!("+") => {
			eat_digits1(lexer, offset)?;
		}
		TokenKind::Digits => {}
		x => bail!("Unexpected token {x}, expected integer",@start.span),
	};

	if has_ident_after(lexer) {
		let char = lexer.reader.next().expect("lexer validated input");
		let char = lexer.reader.convert_to_char(char)?;
		bail!("Invalid token, found unexpected character `{char} after integer token", @lexer.current_span())
	}

	let last_offset = lexer.reader.offset();
	let peek = lexer.reader.peek();
	if peek == Some(b'.') {
		let is_mantissa = lexer.reader.peek1().map(|x| x.is_ascii_digit()).unwrap_or(false);
		if is_mantissa {
			let span = Span {
				offset: last_offset,
				len: 1,
			};
			bail!("Unexpected character `.` starting float, only integers are allowed here", @span)
		}
	}

	if peek == Some(b'e') || peek == Some(b'E') {
		bail!("Unexpected character `{}` only integers are allowed here",peek.expect("validated input") as char, @lexer.current_span())
	}

	let span = lexer.current_span();
	let str = prepare_number_str(lexer.span_str(span));
	str.parse().map_err(|e| syntax_error!("Invalid integer: {e}", @span))
}

pub trait Float: Sized {
	const NAN: Self;
	const NEG_INFINITY: Self;
	const INFINITY: Self;

	fn from_str(s: &str) -> Result<Self, ParseFloatError>;
}

impl Float for f64 {
	const NAN: Self = f64::NAN;

	const NEG_INFINITY: Self = f64::NEG_INFINITY;

	const INFINITY: Self = f64::INFINITY;

	fn from_str(s: &str) -> Result<Self, ParseFloatError> {
		s.parse()
	}
}
impl Float for f32 {
	const NAN: Self = f32::NAN;

	const NEG_INFINITY: Self = f32::NEG_INFINITY;

	const INFINITY: Self = f32::INFINITY;

	fn from_str(s: &str) -> Result<Self, ParseFloatError> {
		s.parse()
	}
}

/// Generic integer parsing method,
/// works for all unsigned integers.
pub fn float<F>(lexer: &mut Lexer, start: Token) -> Result<F, SyntaxError>
where
	F: Float,
{
	let offset = start.span.offset;
	match start.kind {
		t!("-") => {
			if eat_infinity(lexer, offset)? {
				return Ok(F::NEG_INFINITY);
			}
			eat_digits1(lexer, offset)?;
		}
		t!("+") => {
			if eat_infinity(lexer, offset)? {
				return Ok(F::INFINITY);
			}
			eat_digits1(lexer, offset)?;
		}
		TokenKind::Digits => {}
		TokenKind::NaN => return Ok(F::NAN),
		TokenKind::Infinity => return Ok(F::INFINITY),
		x => bail!("Unexpected token {x}, expected floating point number",@start.span),
	};

	let before_mantissa = lexer.reader.offset();
	if lexer.eat(b'.') {
		eat_digits1(lexer, before_mantissa)?;
	}

	let before_exponent = lexer.reader.offset();
	if lexer.eat(b'e') || lexer.eat(b'E') {
		if !lexer.eat(b'-') {
			lexer.eat(b'+');
		}

		eat_digits1(lexer, before_exponent)?;
	}

	let number_span = lexer.current_span();

	lexer.eat(b'f');

	if has_ident_after(lexer) {
		let char = lexer.reader.next().expect("lexer validated input");
		let char = lexer.reader.convert_to_char(char)?;
		bail!("Invalid token, found invalid character `{char}` after number token", @lexer.current_span())
	}

	let str = prepare_number_str(lexer.span_str(number_span));
	F::from_str(str.as_ref())
		.map_err(|e| syntax_error!("Invalid floating point number: {e}", @lexer.current_span()))
}

pub fn duration(lexer: &mut Lexer, start: Token) -> Result<Duration, SyntaxError> {
	match start.kind {
		TokenKind::Digits => {}
		x => bail!("Unexpected token {x}, expected duration", @start.span),
	}

	let mut duration = Duration::ZERO;

	let mut number_span = start.span;
	loop {
		let suffix = lex_duration_suffix(lexer)?;

		let numeric_string = prepare_number_str(lexer.span_str(number_span));
		let numeric_value: u64 = numeric_string.parse().map_err(
			|e| syntax_error!("Invalid token, failed to parse duration digits: {e}",@lexer.current_span()),
		)?;

		let addition = match suffix {
			DurationSuffix::Nano => Duration::from_nanos(numeric_value),
			DurationSuffix::Micro => Duration::from_micros(numeric_value),
			DurationSuffix::Milli => Duration::from_millis(numeric_value),
			DurationSuffix::Second => Duration::from_secs(numeric_value),
			DurationSuffix::Minute => {
				let minutes = numeric_value.checked_mul(SECONDS_PER_MINUTE).ok_or_else(
					|| syntax_error!("Invalid duration, value overflowed maximum allowed value", @lexer.current_span()),
				)?;
				Duration::from_secs(minutes)
			}
			DurationSuffix::Hour => {
				let hours = numeric_value.checked_mul(SECONDS_PER_HOUR).ok_or_else(
					|| syntax_error!("Invalid duration, value overflowed maximum allowed value", @lexer.current_span()),
				)?;
				Duration::from_secs(hours)
			}
			DurationSuffix::Day => {
				let day = numeric_value.checked_mul(SECONDS_PER_DAY).ok_or_else(
					|| syntax_error!("Invalid duration, value overflowed maximum allowed value", @lexer.current_span()),
				)?;
				Duration::from_secs(day)
			}
			DurationSuffix::Week => {
				let week = numeric_value.checked_mul(SECONDS_PER_WEEK).ok_or_else(
					|| syntax_error!("Invalid duration, value overflowed maximum allowed value", @lexer.current_span()),
				)?;
				Duration::from_secs(week)
			}
			DurationSuffix::Year => {
				let year = numeric_value.checked_mul(SECONDS_PER_YEAR).ok_or_else(
					|| syntax_error!("Invalid duration, value overflowed maximum allowed value", @lexer.current_span()),
				)?;
				Duration::from_secs(year)
			}
		};

		duration = duration.checked_add(addition).ok_or_else(
			|| syntax_error!("Invalid duration, value overflowed maximum allowed value", @lexer.current_span()),
		)?;

		match lexer.reader.peek() {
			Some(x) if x.is_ascii_digit() => {
				let before = lexer.reader.offset();
				eat_digits(lexer);
				number_span = lexer.span_since(before);
			}
			_ => break,
		}
	}

	Ok(duration)
}

fn lex_duration_suffix(lexer: &mut Lexer) -> Result<DurationSuffix, SyntaxError> {
	let suffix = match lexer.reader.next() {
		Some(b'n') => {
			lexer.expect('s')?;
			DurationSuffix::Nano
		}
		Some(b'u') => {
			lexer.expect('s')?;
			DurationSuffix::Micro
		}
		Some(b'm') => {
			if lexer.eat(b's') {
				DurationSuffix::Milli
			} else {
				DurationSuffix::Minute
			}
		}
		Some(b's') => DurationSuffix::Second,
		Some(b'h') => DurationSuffix::Hour,
		Some(b'd') => DurationSuffix::Day,
		Some(b'w') => DurationSuffix::Week,
		Some(b'y') => DurationSuffix::Year,
		// Start byte of 'µ'
		Some(0xC2) => {
			if !lexer.eat(0xB5) {
				let char = lexer.reader.complete_char(0xC2)?;
				bail!("Invalid duration token, expected a duration suffix found `{char}`",@lexer.current_span())
			}
			lexer.expect('s')?;
			DurationSuffix::Micro
		}
		Some(x) => {
			let char = lexer.reader.convert_to_char(x)?;
			bail!("Invalid duration token, expected a duration suffix found `{char}`",@lexer.current_span())
		}
		None => {
			bail!("Unexpected end of file, expected a duration suffix",@lexer.current_span())
		}
	};

	if has_ident_after(lexer) {
		let char = lexer.reader.next().expect("lexer validated input");
		let char = lexer.reader.convert_to_char(char)?;
		bail!("Invalid token, found invalid character `{char}` after duration suffix", @lexer.current_span())
	}

	Ok(suffix)
}

fn has_ident_after(lexer: &mut Lexer) -> bool {
	match lexer.reader.peek() {
		Some(x) => x.is_ascii_alphabetic(),
		None => false,
	}
}

fn eat_digits1(lexer: &mut Lexer, start: u32) -> Result<(), SyntaxError> {
	match lexer.reader.peek() {
		Some(x) if x.is_ascii_digit() => {}
		Some(x) => {
			let char = lexer.reader.convert_to_char(x)?;
			bail!("Invalid number token, expected a digit, found: {char}", @lexer.span_since(start));
		}
		None => {
			bail!("Unexpected end of file, expected a number token digit", @lexer.span_since(start));
		}
	}

	eat_digits(lexer);
	Ok(())
}

fn eat_digits(lexer: &mut Lexer) {
	while lexer.eat_when(|x| x.is_ascii_digit() || x == b'_') {}
}

fn eat_infinity(lexer: &mut Lexer, start: u32) -> Result<bool, SyntaxError> {
	if lexer.reader.eat(b'I') {
		for b in b"nfinity" {
			match lexer.reader.next() {
				Some(x) if x == *b => {}
				Some(x) => {
					bail!("Invalid number token, expected `{}` found: {x}",*b as char, @lexer.span_since(start))
				}
				None => {
					bail!("Unexpected end of file, expected a number token digit", @lexer.span_since(start));
				}
			}
		}
		Ok(true)
	} else {
		Ok(false)
	}
}
