use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use dateparser::parse;
use dotenvy::dotenv;
use lazy_static::lazy_static;
use regex::Regex;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize, Serializer};
use serde_json::Value;
use std::cmp::Ordering;
use std::ops::Deref;
use std::{env, fmt};

pub use accept::{ApAccept, ApAcceptType};
pub use activity::ApActivity;
pub use actor::{
    ApActor, ApActorTerse, ApActorType, ApAddress, ApCapabilities, ApPublicKey, PUBLIC_COLLECTION,
};
pub use add::{ApAdd, ApAddType};
pub use announce::{ApAnnounce, ApAnnounceType};
pub use attachment::{ApAttachment, ApDocument, ApLink, ApProof, ApVerifiableIdentityStatement};
pub use block::{ApBlock, ApBlockType};
pub use collection::{
    ActorsPage, ApCollection, ApCollectionType, Collectible, FollowersPage, LeadersPage,
};
pub use create::{ApCreate, ApCreateType};
pub use delete::{ApDelete, ApDeleteType, ApTombstone};
pub use follow::{ApFollow, ApFollowType};
pub use image::{ApImage, ApImageType};
pub use like::{ApLike, ApLikeType};
pub use note::{ApNote, ApNoteType, Metadata};
pub use object::{
    ApContext, ApEndpoint, ApHashtag, ApHashtagType, ApMention, ApMentionType, ApObject, ApTag,
    ApTimelineObject,
};
pub use question::{ApQuestion, ApQuestionType};
pub use video::{ApVideo, ApVideoType};
//pub use remove::{ApRemove, ApRemoveType};
pub use session::{ApInstrument, ApInstrumentType, ApSession};
pub use signature::{ApSignature, ApSignatureType};

#[cfg(not(target_arch = "wasm32"))]
pub use signature::verify_jsonld_signature;

pub use jdt_maybe_multiple::MaybeMultiple;
pub use jdt_maybe_reference::Identifier;
pub use jdt_maybe_reference::MaybeReference;
pub use undo::{ApUndo, ApUndoType};
pub use update::{ApUpdate, ApUpdateType};

pub mod accept;
pub mod activity;
pub mod actor;
pub mod add;
pub mod announce;
pub mod attachment;
pub mod block;
pub mod collection;
pub mod create;
pub mod delete;
pub mod follow;
pub mod image;
pub mod like;
pub mod note;
pub mod object;
pub mod question;
pub mod session;
pub mod signature;
pub mod undo;
pub mod update;
pub mod video;

lazy_static! {
    pub static ref IMAGE_MEDIA_RE: Regex =
        Regex::new(r#"^image\/([a-z]+)$"#).expect("invalid image media regex");
    pub static ref MEDIA_DIR: String = {
        dotenv().ok();
        env::var("MEDIA_DIR").expect("MEDIA_DIR must be set")
    };
    pub static ref SERVER_URL: String = {
        dotenv().ok();
        env::var("SERVER_URL").expect("SERVER_URL must be set")
    };
    pub static ref SERVER_NAME: String = {
        dotenv().ok();
        env::var("SERVER_NAME").expect("SERVER_NAME must be set")
    };
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct Ephemeral {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub followers: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub leaders: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub following: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub leader_as_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub follow_activity_as_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary_markdown: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actors: Option<Vec<ApActorTerse>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub announces: Option<Vec<ApActorTerse>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub liked: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub announced: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub targeted: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<Vec<Metadata>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub likes: Option<Vec<ApActorTerse>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub attributed_to: Option<Vec<ApActorTerse>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub internal_uuid: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub webfinger: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum ActivityPub {
    Activity(ApActivity),
    Actor(ApActor),
    Object(ApObject),
    Reference(String),
}

impl<T> TryFrom<MaybeReference<T>> for ActivityPub {
    type Error = anyhow::Error;

    fn try_from(reference: MaybeReference<T>) -> Result<Self> {
        Ok(ActivityPub::Reference(
            reference
                .reference()
                .ok_or(anyhow!("MaybeReference is not a Reference"))?,
        ))
    }
}

impl TryFrom<Value> for ActivityPub {
    type Error = anyhow::Error;

    fn try_from(value: Value) -> Result<Self> {
        serde_json::from_value(value).map_err(anyhow::Error::msg)
    }
}

impl FromIterator<ApObject> for Vec<ActivityPub> {
    fn from_iter<I: IntoIterator<Item = ApObject>>(iter: I) -> Self {
        iter.into_iter().map(ActivityPub::from).collect()
    }
}

impl FromIterator<ApActivity> for Vec<ActivityPub> {
    fn from_iter<I: IntoIterator<Item = ApActivity>>(iter: I) -> Self {
        iter.into_iter().map(ActivityPub::from).collect()
    }
}

impl From<ApActivity> for ActivityPub {
    fn from(activity: ApActivity) -> Self {
        ActivityPub::Activity(activity)
    }
}

impl From<&ApActivity> for ActivityPub {
    fn from(activity: &ApActivity) -> Self {
        ActivityPub::Activity(activity.clone())
    }
}

impl From<ApObject> for ActivityPub {
    fn from(object: ApObject) -> Self {
        ActivityPub::Object(object)
    }
}

impl From<&ApObject> for ActivityPub {
    fn from(object: &ApObject) -> Self {
        ActivityPub::Object(object.clone())
    }
}

impl From<ApActor> for ActivityPub {
    fn from(actor: ApActor) -> Self {
        ActivityPub::Actor(actor)
    }
}

impl ActivityPub {
    pub fn timestamp(&self) -> DateTime<Utc> {
        match self {
            ActivityPub::Object(object) => object.timestamp(),
            ActivityPub::Activity(activity) => match activity {
                ApActivity::Create(create) => create
                    .ephemeral
                    .clone()
                    .and_then(|x| x.created_at)
                    .unwrap_or(Utc::now()),
                ApActivity::Announce(announce) => announce
                    .ephemeral
                    .clone()
                    .and_then(|x| x.created_at)
                    .unwrap_or(Utc::now()),
                _ => Utc::now(),
            },
            _ => Utc::now(),
        }
    }

    pub fn time(timestamp: DateTime<Utc>) -> String {
        timestamp.format("%Y-%m-%dT%H:%M:%SZ").to_string()
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct ApDateTime(DateTime<Utc>);

impl ApDateTime {
    fn now() -> Self {
        ApDateTime(Utc::now())
    }
}

impl Serialize for ApDateTime {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        ActivityPub::time(self.0).serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for ApDateTime {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;

        // Try chrono's RFC3339 parser first
        if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
            return Ok(ApDateTime(dt.with_timezone(&Utc)));
        }

        // Try dateparser for other common formats
        if let Ok(dt) = parse(&s) {
            return Ok(ApDateTime(dt));
        }

        // Try the specific format "YYYY-MM-DDTHH:MM:SS+ZZZZ"
        if let Ok(dt) = DateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%z") {
            return Ok(ApDateTime(dt.with_timezone(&Utc)));
        }

        // Fall back to our custom format for the period-before-timezone case
        if let Ok(dt) = DateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S.%z") {
            return Ok(ApDateTime(dt.with_timezone(&Utc)));
        }

        Err(serde::de::Error::custom(format!(
            "invalid datetime format: {}",
            s
        )))
    }
}

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

impl Deref for ApDateTime {
    type Target = DateTime<Utc>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<DateTime<Utc>> for ApDateTime {
    fn from(date: DateTime<Utc>) -> Self {
        ApDateTime(date)
    }
}

pub trait Temporal {
    fn published(&self) -> String;
    fn created_at(&self) -> Option<DateTime<Utc>>;
    fn updated_at(&self) -> Option<DateTime<Utc>>;
}

impl From<Option<Vec<ApActorTerse>>> for Ephemeral {
    fn from(actors: Option<Vec<ApActorTerse>>) -> Self {
        Self {
            actors,
            ..Default::default()
        }
    }
}

#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct OrdValue(Value);

impl PartialOrd for OrdValue {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for OrdValue {
    fn cmp(&self, other: &Self) -> Ordering {
        let a_str = serde_json::to_string(&self.0).unwrap();
        let b_str = serde_json::to_string(&other.0).unwrap();
        a_str.cmp(&b_str)
    }
}

pub fn get_followers_ap_id_from_username(username: String) -> String {
    format!("{}/user/{username}/followers", *crate::SERVER_URL)
}

pub fn get_following_ap_id_from_username(username: String) -> String {
    format!("{}/user/{username}/following", *crate::SERVER_URL)
}
pub fn get_session_as_id_from_uuid(uuid: String) -> String {
    format!("{}/sessions/{uuid}", *crate::SERVER_URL)
}
