use arrayvec::ArrayString;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::str::FromStr;
use vitaminc::random::{Generatable, SafeRand};

const ACTOR_ID_BYTE_LEN: usize = 10;
const ACTOR_ID_ENCODED_LEN: usize = 16;
const ALPHABET: base32::Alphabet = base32::Alphabet::Rfc4648 { padding: false };

type ActorIdString = ArrayString<ACTOR_ID_ENCODED_LEN>;

/// The kind of actor — determines the prefix in the serialized `ActorIdentifier`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ActorKind {
    App,
    Agent,
}

impl ActorKind {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::App => "app",
            Self::Agent => "agent",
        }
    }

    pub fn parse(s: &str) -> Option<Self> {
        match s {
            "app" => Some(Self::App),
            "agent" => Some(Self::Agent),
            _ => None,
        }
    }
}

/// A compact, stack-allocated unique identifier for an actor.
///
/// Contains only the random base32-encoded portion (16 chars / 10 bytes),
/// matching the format of [`WorkspaceId`](crate::WorkspaceId).
/// Used as the primary key in the `actors` table.
///
/// For the full identifier including the actor kind prefix (e.g. `"app-JBSWY3DPEHPK3PXP"`),
/// see [`ActorIdentifier`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
#[cfg_attr(
    feature = "server",
    derive(diesel::expression::AsExpression, diesel::deserialize::FromSqlRow)
)]
#[cfg_attr(feature = "server", diesel(sql_type = diesel::sql_types::Text))]
pub struct ActorId(ActorIdString);

impl ActorId {
    /// Generate a new random `ActorId`.
    pub fn generate() -> Result<Self, vitaminc::random::RandomError> {
        let mut rng = SafeRand::from_entropy()?;
        let buf: [u8; ACTOR_ID_BYTE_LEN] = Generatable::random(&mut rng)?;
        let encoded = base32::encode(ALPHABET, &buf);
        let mut id = ActorIdString::new();
        id.push_str(&encoded);
        Ok(Self(id))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

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

impl TryFrom<&str> for ActorId {
    type Error = InvalidActorId;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        if is_valid_actor_id(value) {
            let mut id = ActorIdString::new();
            id.push_str(value);
            Ok(Self(id))
        } else {
            Err(InvalidActorId(value.to_string()))
        }
    }
}

impl TryFrom<String> for ActorId {
    type Error = InvalidActorId;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::try_from(value.as_str())
    }
}

impl FromStr for ActorId {
    type Err = InvalidActorId;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::try_from(s)
    }
}

impl PartialEq<&str> for ActorId {
    fn eq(&self, other: &&str) -> bool {
        self.0.as_str() == *other
    }
}

/// A full actor identifier combining the actor kind and unique ID.
///
/// Serializes as `"app-JBSWY3DPEHPK3PXP"` or `"agent-JBSWY3DPEHPK3PXP"`.
/// Used in JWT claims and external APIs.
///
/// For just the unique ID portion (used as the database primary key),
/// see [`ActorId`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ActorIdentifier {
    kind: ActorKind,
    id: ActorId,
}

impl ActorIdentifier {
    /// Create an `ActorIdentifier` from a kind and ID.
    pub fn new(kind: ActorKind, id: ActorId) -> Self {
        Self { kind, id }
    }

    /// Generate a new random `ActorIdentifier` of the given kind.
    pub fn generate(kind: ActorKind) -> Result<Self, vitaminc::random::RandomError> {
        let id = ActorId::generate()?;
        Ok(Self { kind, id })
    }

    /// The kind of actor (App or Agent).
    pub fn kind(&self) -> ActorKind {
        self.kind
    }

    /// The unique actor ID.
    pub fn id(&self) -> ActorId {
        self.id
    }
}

impl Display for ActorIdentifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}-{}", self.kind.as_str(), self.id)
    }
}

impl TryFrom<&str> for ActorIdentifier {
    type Error = InvalidActorId;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let (prefix, id_str) = value
            .split_once('-')
            .ok_or_else(|| InvalidActorId(value.to_string()))?;

        let kind = ActorKind::parse(prefix).ok_or_else(|| InvalidActorId(value.to_string()))?;
        let id = ActorId::try_from(id_str)?;

        Ok(Self { kind, id })
    }
}

impl FromStr for ActorIdentifier {
    type Err = InvalidActorId;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::try_from(s)
    }
}

impl Serialize for ActorIdentifier {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for ActorIdentifier {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Self::try_from(s.as_str()).map_err(serde::de::Error::custom)
    }
}

fn is_valid_actor_id(id: &str) -> bool {
    base32::decode(ALPHABET, id)
        .map(|bytes| bytes.len() == ACTOR_ID_BYTE_LEN)
        .unwrap_or(false)
}

#[derive(Debug, thiserror::Error)]
#[error("Invalid actor ID: {0}")]
pub struct InvalidActorId(String);

#[cfg(feature = "server")]
mod sql_types {
    use super::ActorId;
    use diesel::{
        backend::Backend,
        deserialize::{self, FromSql},
        serialize::{self, Output, ToSql},
        sql_types::Text,
    };

    impl<DB> ToSql<Text, DB> for ActorId
    where
        DB: Backend,
        str: ToSql<Text, DB>,
    {
        fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
            self.0.to_sql(out)
        }
    }

    impl<DB> FromSql<Text, DB> for ActorId
    where
        DB: Backend,
        String: FromSql<Text, DB>,
    {
        fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
            let raw = String::from_sql(bytes)?;
            let actor_id = ActorId::try_from(raw)?;
            Ok(actor_id)
        }
    }
}

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

    mod actor_id {
        use super::*;

        #[test]
        fn generate_produces_valid_id() {
            let id = ActorId::generate().unwrap();
            assert_eq!(id.as_str().len(), 16, "base32 ID should be 16 chars");
        }

        #[test]
        fn round_trips_through_serde() {
            let id = ActorId::generate().unwrap();
            let json = serde_json::to_value(id).unwrap();
            let parsed: ActorId = serde_json::from_value(json).unwrap();
            assert_eq!(parsed, id);
        }

        #[test]
        fn from_str_round_trips() {
            let id = ActorId::generate().unwrap();
            let s = id.to_string();
            let parsed: ActorId = s.parse().unwrap();
            assert_eq!(parsed, id);
        }

        #[test]
        fn rejects_invalid_base32() {
            assert!(ActorId::try_from("!!!INVALID!!!").is_err());
        }

        #[test]
        fn rejects_wrong_length() {
            assert!(ActorId::try_from("AAAA").is_err());
        }
    }

    mod identifier_app {
        use super::*;

        #[test]
        fn generate_produces_valid_identifier() {
            let ident = ActorIdentifier::generate(ActorKind::App).unwrap();
            assert_eq!(ident.kind(), ActorKind::App, "kind should be App");
            assert_eq!(
                ident.id().as_str().len(),
                16,
                "base32 ID should be 16 chars"
            );
        }

        #[test]
        fn serializes_with_prefix() {
            let ident = ActorIdentifier::generate(ActorKind::App).unwrap();
            let serialized = serde_json::to_value(ident).unwrap();
            let s = serialized.as_str().unwrap();
            assert!(s.starts_with("app-"), "should start with 'app-', got: {s}");
            assert_eq!(s.len(), 20, "app-<16 chars> = 20 chars, got: {s}");
        }

        #[test]
        fn round_trips_through_serde() {
            let ident = ActorIdentifier::generate(ActorKind::App).unwrap();
            let json = serde_json::to_value(ident).unwrap();
            let parsed: ActorIdentifier = serde_json::from_value(json).unwrap();
            assert_eq!(parsed, ident, "should round-trip through serde");
        }
    }

    mod identifier_agent {
        use super::*;

        #[test]
        fn generate_produces_valid_identifier() {
            let ident = ActorIdentifier::generate(ActorKind::Agent).unwrap();
            assert_eq!(ident.kind(), ActorKind::Agent, "kind should be Agent");
            assert_eq!(
                ident.id().as_str().len(),
                16,
                "base32 ID should be 16 chars"
            );
        }

        #[test]
        fn serializes_with_prefix() {
            let ident = ActorIdentifier::generate(ActorKind::Agent).unwrap();
            let serialized = serde_json::to_value(ident).unwrap();
            let s = serialized.as_str().unwrap();
            assert!(
                s.starts_with("agent-"),
                "should start with 'agent-', got: {s}"
            );
            assert_eq!(s.len(), 22, "agent-<16 chars> = 22 chars, got: {s}");
        }

        #[test]
        fn round_trips_through_serde() {
            let ident = ActorIdentifier::generate(ActorKind::Agent).unwrap();
            let json = serde_json::to_value(ident).unwrap();
            let parsed: ActorIdentifier = serde_json::from_value(json).unwrap();
            assert_eq!(parsed, ident, "should round-trip through serde");
        }
    }

    mod identifier_invalid {
        use super::*;

        #[test]
        fn rejects_unknown_prefix() {
            let json = json!("unknown-JBSWY3DPEHPK3PXP");
            let result: Result<ActorIdentifier, _> = serde_json::from_value(json);
            assert!(result.is_err(), "should reject unknown actor kind");
        }

        #[test]
        fn rejects_missing_delimiter() {
            let json = json!("appJBSWY3DPEHPK3PXP");
            let result: Result<ActorIdentifier, _> = serde_json::from_value(json);
            assert!(result.is_err(), "should reject missing delimiter");
        }

        #[test]
        fn rejects_invalid_base32() {
            let json = json!("app-!!!INVALID!!!");
            let result: Result<ActorIdentifier, _> = serde_json::from_value(json);
            assert!(result.is_err(), "should reject invalid base32");
        }

        #[test]
        fn rejects_wrong_length() {
            let json = json!("app-AAAA");
            let result: Result<ActorIdentifier, _> = serde_json::from_value(json);
            assert!(result.is_err(), "should reject wrong-length ID");
        }

        #[test]
        fn rejects_empty_string() {
            let json = json!("");
            let result: Result<ActorIdentifier, _> = serde_json::from_value(json);
            assert!(result.is_err(), "should reject empty string");
        }
    }

    #[test]
    fn from_str_round_trips() {
        let ident = ActorIdentifier::generate(ActorKind::App).unwrap();
        let s = ident.to_string();
        let parsed: ActorIdentifier = s.parse().unwrap();
        assert_eq!(parsed, ident);
    }

    #[test]
    fn from_str_rejects_invalid() {
        assert!("not-valid".parse::<ActorIdentifier>().is_err());
    }

    #[test]
    fn display_matches_serialize() {
        let ident = ActorIdentifier::generate(ActorKind::App).unwrap();
        let display = ident.to_string();
        let serialized = serde_json::to_value(ident).unwrap();
        assert_eq!(
            display,
            serialized.as_str().unwrap(),
            "Display and Serialize should produce the same string"
        );
    }

    #[test]
    fn new_constructs_from_parts() {
        let id = ActorId::generate().unwrap();
        let ident = ActorIdentifier::new(ActorKind::App, id);
        assert_eq!(ident.kind(), ActorKind::App);
        assert_eq!(ident.id(), id);
    }
}
