use surrealdb_types::{SqlFormat, ToSql};

use crate::val::range::{IntegerRangeIter, TypedRange};
use crate::val::{RecordId, RecordIdKey, TableName};

pub(crate) struct IntoIter {
	table: TableName,
	key: IntoIterKey,
}

pub(crate) enum IntoIterKey {
	Count(i64),
	Range(IntegerRangeIter),
}

impl Iterator for IntoIter {
	type Item = RecordId;

	fn next(&mut self) -> Option<RecordId> {
		match self.key {
			IntoIterKey::Count(ref mut c) => {
				if *c == 0 {
					None
				} else {
					*c -= 1;
					Some(RecordId {
						table: self.table.clone(),
						key: RecordIdKey::rand(),
					})
				}
			}
			IntoIterKey::Range(ref mut r) => {
				let k = r.next()?;
				Some(RecordId {
					table: self.table.clone(),
					key: RecordIdKey::from(k),
				})
			}
		}
	}

	fn size_hint(&self) -> (usize, Option<usize>) {
		const MAX: usize = const {
			match usize::BITS {
				64 => i64::MAX as usize,
				32 => usize::MAX,
				_ => panic!("unsupported pointer width"),
			}
		};

		match &self.key {
			IntoIterKey::Count(x) => {
				let x = *x;
				let lower = x.min(MAX as i64) as usize;
				let upper = if x < MAX as i64 {
					Some(x as usize)
				} else {
					None
				};
				(lower, upper)
			}
			IntoIterKey::Range(r) => r.size_hint(),
		}
	}
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) enum Mock {
	Count(TableName, i64),
	Range(TableName, TypedRange<i64>),
}

impl Mock {
	pub(crate) fn table(&self) -> &TableName {
		match self {
			Mock::Count(t, _) => t,
			Mock::Range(t, _) => t,
		}
	}
}

impl IntoIterator for Mock {
	type Item = RecordId;
	type IntoIter = IntoIter;
	fn into_iter(self) -> Self::IntoIter {
		match self {
			Mock::Count(t, k) => IntoIter {
				table: t,
				key: IntoIterKey::Count(k.max(0)),
			},
			Mock::Range(t, r) => IntoIter {
				table: t,
				key: IntoIterKey::Range(r.iter()),
			},
		}
	}
}

impl ToSql for Mock {
	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
		let mock: crate::sql::Mock = self.clone().into();
		mock.fmt_sql(f, sql_fmt);
	}
}
