use std::str::FromStr;

use anyhow::Result;
use rand::Rng;
use rand::distributions::Alphanumeric;
use surrealdb_types::{SqlFormat, ToSql, write_sql};

use super::Expr;
use crate::err::Error;
use crate::fmt::CoverStmts;
use crate::sql::{Algorithm, Literal};

pub(crate) fn random_key() -> String {
	rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect::<String>()
}

/// The type of access methods available
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) enum AccessType {
	Record(RecordAccess),
	Jwt(JwtAccess),
	Bearer(BearerAccess),
}

impl Default for AccessType {
	fn default() -> Self {
		// Access type defaults to the most specific
		Self::Record(RecordAccess {
			..Default::default()
		})
	}
}

impl From<AccessType> for crate::expr::AccessType {
	fn from(v: AccessType) -> Self {
		match v {
			AccessType::Record(v) => Self::Record(v.into()),
			AccessType::Jwt(v) => Self::Jwt(v.into()),
			AccessType::Bearer(v) => Self::Bearer(v.into()),
		}
	}
}

impl From<crate::expr::AccessType> for AccessType {
	fn from(v: crate::expr::AccessType) -> Self {
		match v {
			crate::expr::AccessType::Record(v) => AccessType::Record(v.into()),
			crate::expr::AccessType::Jwt(v) => AccessType::Jwt(v.into()),
			crate::expr::AccessType::Bearer(v) => AccessType::Bearer(v.into()),
		}
	}
}

impl ToSql for AccessType {
	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
		match self {
			AccessType::Jwt(ac) => {
				write_sql!(f, sql_fmt, "JWT {}", ac);
			}
			AccessType::Record(ac) => {
				write_sql!(f, sql_fmt, "RECORD");
				if let Some(ref v) = ac.signup {
					write_sql!(f, sql_fmt, " SIGNUP {}", CoverStmts(v));
				}
				if let Some(ref v) = ac.signin {
					write_sql!(f, sql_fmt, " SIGNIN {}", CoverStmts(v));
				}
				if ac.bearer.is_some() {
					write_sql!(f, sql_fmt, " WITH REFRESH")
				}
				write_sql!(f, sql_fmt, " WITH JWT {}", ac.jwt);
			}
			AccessType::Bearer(ac) => {
				write_sql!(f, sql_fmt, "BEARER");
				match ac.subject {
					BearerAccessSubject::User => write_sql!(f, sql_fmt, " FOR USER"),
					BearerAccessSubject::Record => write_sql!(f, sql_fmt, " FOR RECORD"),
				}
			}
		}
	}
}

impl AccessType {
	/// Returns whether or not the access method can issue non-token grants
	/// In this context, token refers exclusively to JWT
	#[allow(dead_code)]
	pub fn can_issue_grants(&self) -> bool {
		match self {
			// The JWT access method cannot issue stateful grants.
			AccessType::Jwt(_) => false,
			// The record access method can be used to issue grants if defined with bearer AKA
			// refresh.
			AccessType::Record(ac) => ac.bearer.is_some(),
			AccessType::Bearer(_) => true,
		}
	}
	/// Returns whether or not the access method can issue tokens
	/// In this context, tokens refers exclusively to JWT
	#[allow(dead_code)]
	pub fn can_issue_tokens(&self) -> bool {
		match self {
			// The JWT access method can only issue tokens if an issuer is set
			AccessType::Jwt(jwt) => jwt.issue.is_some(),
			_ => true,
		}
	}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct JwtAccess {
	// Verify is required
	pub verify: JwtAccessVerify,
	// Issue is optional
	// It is possible to only verify externally issued tokens
	pub issue: Option<JwtAccessIssue>,
}

//TODO: Move this logic out of the parser
impl Default for JwtAccess {
	fn default() -> Self {
		// Defaults to HS512 with a randomly generated key
		let alg = Algorithm::Hs512;
		let key = random_key();
		// By default the access method can verify and issue tokens
		Self {
			verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
				alg,
				key: Expr::Literal(Literal::String(key.clone())),
			}),
			issue: Some(JwtAccessIssue {
				alg,
				key: Expr::Literal(Literal::String(key)),
			}),
		}
	}
}

impl ToSql for JwtAccess {
	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
		match &self.verify {
			JwtAccessVerify::Key(v) => {
				write_sql!(f, sql_fmt, "ALGORITHM {} KEY {}", v.alg, CoverStmts(&v.key));
			}
			JwtAccessVerify::Jwks(v) => {
				write_sql!(f, sql_fmt, "URL {}", CoverStmts(&v.url));
			}
		}
		if let Some(iss) = &self.issue {
			write_sql!(f, sql_fmt, " WITH ISSUER KEY {}", CoverStmts(&iss.key));
		}
	}
}

impl From<JwtAccess> for crate::expr::JwtAccess {
	fn from(v: JwtAccess) -> Self {
		Self {
			verify: v.verify.into(),
			issue: v.issue.map(Into::into),
		}
	}
}

impl From<crate::expr::JwtAccess> for JwtAccess {
	fn from(v: crate::expr::JwtAccess) -> Self {
		Self {
			verify: v.verify.into(),
			issue: v.issue.map(Into::into),
		}
	}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) struct JwtAccessIssue {
	pub alg: Algorithm,
	pub key: Expr,
}

impl Default for JwtAccessIssue {
	fn default() -> Self {
		// TODO: Move this computation out of the AST
		Self {
			// Defaults to HS512
			alg: Algorithm::Hs512,
			// Avoid defaulting to empty key
			key: Expr::Literal(Literal::String(random_key())),
		}
	}
}

impl From<JwtAccessIssue> for crate::expr::access_type::JwtAccessIssue {
	fn from(v: JwtAccessIssue) -> Self {
		Self {
			alg: v.alg.into(),
			key: v.key.into(),
		}
	}
}

impl From<crate::expr::access_type::JwtAccessIssue> for JwtAccessIssue {
	fn from(v: crate::expr::access_type::JwtAccessIssue) -> Self {
		Self {
			alg: v.alg.into(),
			key: v.key.into(),
		}
	}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) enum JwtAccessVerify {
	Key(JwtAccessVerifyKey),
	Jwks(JwtAccessVerifyJwks),
}

impl From<JwtAccessVerify> for crate::expr::access_type::JwtAccessVerify {
	fn from(v: JwtAccessVerify) -> Self {
		match v {
			JwtAccessVerify::Key(v) => Self::Key(v.into()),
			JwtAccessVerify::Jwks(v) => Self::Jwks(v.into()),
		}
	}
}

impl From<crate::expr::access_type::JwtAccessVerify> for JwtAccessVerify {
	fn from(v: crate::expr::access_type::JwtAccessVerify) -> Self {
		match v {
			crate::expr::access_type::JwtAccessVerify::Key(v) => Self::Key(v.into()),
			crate::expr::access_type::JwtAccessVerify::Jwks(v) => Self::Jwks(v.into()),
		}
	}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) struct JwtAccessVerifyKey {
	pub alg: Algorithm,
	pub key: Expr,
}

impl From<JwtAccessVerifyKey> for crate::expr::access_type::JwtAccessVerifyKey {
	fn from(v: JwtAccessVerifyKey) -> Self {
		Self {
			alg: v.alg.into(),
			key: v.key.into(),
		}
	}
}

impl From<crate::expr::access_type::JwtAccessVerifyKey> for JwtAccessVerifyKey {
	fn from(v: crate::expr::access_type::JwtAccessVerifyKey) -> Self {
		Self {
			alg: v.alg.into(),
			key: v.key.into(),
		}
	}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) struct JwtAccessVerifyJwks {
	pub url: Expr,
}

impl From<JwtAccessVerifyJwks> for crate::expr::access_type::JwtAccessVerifyJwks {
	fn from(v: JwtAccessVerifyJwks) -> Self {
		Self {
			url: v.url.into(),
		}
	}
}

impl From<crate::expr::access_type::JwtAccessVerifyJwks> for JwtAccessVerifyJwks {
	fn from(v: crate::expr::access_type::JwtAccessVerifyJwks) -> Self {
		Self {
			url: v.url.into(),
		}
	}
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) struct RecordAccess {
	pub signup: Option<Expr>,
	pub signin: Option<Expr>,
	pub jwt: JwtAccess,
	pub bearer: Option<BearerAccess>,
}

impl From<RecordAccess> for crate::expr::RecordAccess {
	fn from(v: RecordAccess) -> Self {
		Self {
			signup: v.signup.map(Into::into),
			signin: v.signin.map(Into::into),
			jwt: v.jwt.into(),
			bearer: v.bearer.map(Into::into),
		}
	}
}

impl From<crate::expr::RecordAccess> for RecordAccess {
	fn from(v: crate::expr::RecordAccess) -> Self {
		Self {
			signup: v.signup.map(Into::into),
			signin: v.signin.map(Into::into),
			jwt: v.jwt.into(),
			bearer: v.bearer.map(Into::into),
		}
	}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) struct BearerAccess {
	pub kind: BearerAccessType,
	pub subject: BearerAccessSubject,
	pub jwt: JwtAccess,
}

impl Default for BearerAccess {
	fn default() -> Self {
		Self {
			kind: BearerAccessType::Bearer,
			subject: BearerAccessSubject::User,
			jwt: JwtAccess::default(),
		}
	}
}

impl From<BearerAccess> for crate::expr::access_type::BearerAccess {
	fn from(v: BearerAccess) -> Self {
		Self {
			kind: v.kind.into(),
			subject: v.subject.into(),
			jwt: v.jwt.into(),
		}
	}
}

impl From<crate::expr::access_type::BearerAccess> for BearerAccess {
	fn from(v: crate::expr::access_type::BearerAccess) -> Self {
		Self {
			kind: v.kind.into(),
			subject: v.subject.into(),
			jwt: v.jwt.into(),
		}
	}
}

#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub(crate) enum BearerAccessType {
	Bearer,
	Refresh,
}

impl FromStr for BearerAccessType {
	type Err = Error;
	fn from_str(s: &str) -> Result<Self, Self::Err> {
		match s.to_ascii_lowercase().as_str() {
			"bearer" => Ok(Self::Bearer),
			"refresh" => Ok(Self::Refresh),
			_ => Err(Error::AccessGrantBearerInvalid),
		}
	}
}

impl From<BearerAccessType> for crate::expr::access_type::BearerAccessType {
	fn from(v: BearerAccessType) -> Self {
		match v {
			BearerAccessType::Bearer => Self::Bearer,
			BearerAccessType::Refresh => Self::Refresh,
		}
	}
}

impl From<crate::expr::access_type::BearerAccessType> for BearerAccessType {
	fn from(v: crate::expr::access_type::BearerAccessType) -> Self {
		match v {
			crate::expr::access_type::BearerAccessType::Bearer => Self::Bearer,
			crate::expr::access_type::BearerAccessType::Refresh => Self::Refresh,
		}
	}
}

#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum BearerAccessSubject {
	Record,
	User,
}

impl From<BearerAccessSubject> for crate::expr::access_type::BearerAccessSubject {
	fn from(v: BearerAccessSubject) -> Self {
		match v {
			BearerAccessSubject::Record => Self::Record,
			BearerAccessSubject::User => Self::User,
		}
	}
}

impl From<crate::expr::access_type::BearerAccessSubject> for BearerAccessSubject {
	fn from(v: crate::expr::access_type::BearerAccessSubject) -> Self {
		match v {
			crate::expr::access_type::BearerAccessSubject::Record => Self::Record,
			crate::expr::access_type::BearerAccessSubject::User => Self::User,
		}
	}
}
