use std::sync::Arc;

use async_trait::async_trait;
use domain::{
    ports::UserRepository,
    value_objects::{InstanceIdentity, UserId},
};
use k_ap::{ApProfileField, ApUser, ApUserRepository};
use url::Url;

pub struct DomainUserRepoAdapter {
    pub repo: Arc<dyn UserRepository>,
    pub instance: InstanceIdentity,
}

impl DomainUserRepoAdapter {
    pub fn new(repo: Arc<dyn UserRepository>, instance: InstanceIdentity) -> Self {
        Self { repo, instance }
    }

    fn build_user(&self, u: &domain::models::User) -> ApUser {
        let avatar_url = u
            .avatar_path()
            .and_then(|p| Url::parse(&self.instance.image_url_for(p)).ok());
        let banner_url = u
            .banner_path()
            .and_then(|p| Url::parse(&self.instance.image_url_for(p)).ok());
        let profile_url = Url::parse(&format!(
            "{}/u/{}",
            self.instance.base_url(),
            u.username().value()
        ))
        .ok();
        ApUser {
            id: u.id().value(),
            username: u.username().value().to_string(),
            display_name: u.display_name().map(|s| s.to_string()),
            bio: u.bio().map(|s| s.to_string()),
            avatar_url,
            banner_url,
            also_known_as: u
                .also_known_as()
                .map(|s| vec![s.to_string()])
                .unwrap_or_default(),
            profile_url,
            attachment: u
                .profile_fields()
                .iter()
                .map(|f| ApProfileField {
                    name: f.name.clone(),
                    value: f.value.clone(),
                })
                .collect(),
            manually_approves_followers: true,
            discoverable: true,
            actor_type: Default::default(),
            featured_url: Url::parse(&format!("{}/featured", self.instance.actor_url_for(u.id())))
                .ok(),
        }
    }
}

#[async_trait]
impl ApUserRepository for DomainUserRepoAdapter {
    async fn find_by_id(&self, id: uuid::Uuid) -> anyhow::Result<Option<ApUser>> {
        let user_id = UserId::from_uuid(id);
        let user = match self.repo.find_by_id(&user_id).await? {
            Some(u) => u,
            None => return Ok(None),
        };
        Ok(Some(self.build_user(&user)))
    }

    async fn find_by_username(&self, username: &str) -> anyhow::Result<Option<ApUser>> {
        use domain::value_objects::Username;
        let uname =
            Username::new(username.to_string()).map_err(|e| anyhow::anyhow!(e.to_string()))?;
        let user = match self.repo.find_by_username(&uname).await? {
            Some(u) => u,
            None => return Ok(None),
        };
        Ok(Some(self.build_user(&user)))
    }

    async fn count_users(&self) -> anyhow::Result<usize> {
        Ok(self
            .repo
            .list_with_stats()
            .await
            .map_err(|e| anyhow::anyhow!(e.to_string()))?
            .len())
    }
}
