use std::sync::Arc;
use std::time::Duration;
use std::collections::HashSet;

use crate::state::AppState;
use super::inbox::check_is_cruciverb;

pub fn start_discovery_worker(state: Arc<AppState>) {
    tokio::spawn(async move {
        // wait 2 minutes before first run to let the server stabilize
        tokio::time::sleep(Duration::from_secs(120)).await;
        loop {
            if state.ap.is_some() && !super::is_frozen() {
                discover_peers(&state).await;
            }
            // run every 6 hours
            tokio::time::sleep(Duration::from_secs(6 * 3600)).await;
        }
    });
}

async fn discover_peers(state: &Arc<AppState>) {
    let ap = match state.ap.as_ref() {
        Some(ap) => ap,
        None => return,
    };

    // collect known domains (peers + followers) to avoid re-checking
    let known_domains: HashSet<String> = {
        let dbi = state.db.lock().await;
        let mut domains = HashSet::new();
        domains.insert(ap.domain.clone());

        if let Ok(mut stmt) = dbi.db.prepare("SELECT domain FROM ap_peers") {
            if let Ok(rows) = stmt.query_map([], |row| row.get::<_, String>(0)) {
                for r in rows.flatten() { domains.insert(r); }
            }
        }
        if let Ok(mut stmt) = dbi.db.prepare("SELECT domain FROM ap_blocked") {
            if let Ok(rows) = stmt.query_map([], |row| row.get::<_, String>(0)) {
                for r in rows.flatten() { domains.insert(r); }
            }
        }
        domains
    };

    // search our own outbox for posts with cruciverb tags to find
    // domains that have interacted with us (via boosts, likes, etc.)
    // and check peer outboxes for cruciverb-tagged posts from unknown domains

    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()
        .unwrap_or_default();

    // strategy 1: check known peers' followers for other cruciverb instances
    let peer_domains: Vec<String> = {
        let dbi = state.db.lock().await;
        let mut stmt = dbi.db.prepare("SELECT domain, actor_uri FROM ap_peers").unwrap();
        stmt.query_map([], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
            .unwrap().filter_map(|r| r.ok()).map(|(d, _)| d).collect()
    };

    for peer_domain in &peer_domains {
        // fetch peer's followers to find other cruciverb instances
        let followers_url = format!("https://{}/@cruciverb/followers", peer_domain);
        if let Ok(resp) = client.get(&followers_url)
            .header("Accept", "application/activity+json")
            .send().await
        {
            if let Ok(body) = resp.json::<serde_json::Value>().await {
                if let Some(items) = body.get("orderedItems").and_then(|i| i.as_array()) {
                    for item in items {
                        if let Some(uri) = item.as_str() {
                            let domain = uri.split("//").nth(1)
                                .and_then(|s| s.split('/').next())
                                .unwrap_or("");
                            if !domain.is_empty() && !known_domains.contains(domain) {
                                try_discover_instance(state, domain).await;
                            }
                        }
                    }
                }
            }
        }
    }

    // strategy 2: search for the hashtag on known Mastodon/Pleroma instances
    // these have public hashtag timeline APIs
    let follower_domains: Vec<String> = {
        let dbi = state.db.lock().await;
        let mut domains = Vec::new();
        if let Ok(mut stmt) = dbi.db.prepare("SELECT actor_uri FROM ap_followers WHERE accepted = 1") {
            if let Ok(rows) = stmt.query_map([], |row| row.get::<_, String>(0)) {
                for uri in rows.flatten() {
                    if let Some(domain) = uri.split("//").nth(1).and_then(|s| s.split('/').next()) {
                        if !domains.contains(&domain.to_string()) {
                            domains.push(domain.to_string());
                        }
                    }
                }
            }
        }
        domains
    };

    for domain in &follower_domains {
        // Mastodon API: GET /api/v1/timelines/tag/cruciverb-clue
        let tag_url = format!("https://{}/api/v1/timelines/tag/cruciverb-clue?limit=20", domain);
        if let Ok(resp) = client.get(&tag_url).send().await {
            if let Ok(posts) = resp.json::<Vec<serde_json::Value>>().await {
                for post in &posts {
                    // extract the account's domain
                    if let Some(acct) = post.get("account")
                        .and_then(|a| a.get("url"))
                        .and_then(|u| u.as_str())
                    {
                        let post_domain = acct.split("//").nth(1)
                            .and_then(|s| s.split('/').next())
                            .unwrap_or("");
                        if !post_domain.is_empty() && !known_domains.contains(post_domain) {
                            try_discover_instance(state, post_domain).await;
                        }
                    }
                }
            }
        }
    }
}

async fn try_discover_instance(state: &Arc<AppState>, domain: &str) {
    if check_is_cruciverb(domain).await {
        let ap = match state.ap.as_ref() {
            Some(ap) => ap,
            None => return,
        };

        let actor_uri = format!("https://{}/@cruciverb", domain);
        if crate::security::is_internal_url(&actor_uri) {
            tracing::warn!("refusing to discover internal URL: {}", actor_uri);
            return;
        }
        let client = reqwest::Client::builder()
            .timeout(Duration::from_secs(10))
            .build()
            .unwrap_or_default();

        // fetch their actor document for inbox and key
        if let Ok(resp) = client.get(&actor_uri)
            .header("Accept", "application/activity+json")
            .send().await
        {
            if let Ok(actor) = resp.json::<serde_json::Value>().await {
                let inbox = actor.get("inbox").and_then(|v| v.as_str()).unwrap_or("");
                let key_pem = actor.get("publicKey")
                    .and_then(|pk| pk.get("publicKeyPem"))
                    .and_then(|v| v.as_str())
                    .unwrap_or("");

                if !inbox.is_empty() {
                    let dbi = state.db.lock().await;
                    let now = chrono::Utc::now().to_rfc3339();
                    dbi.db.execute(
                        "INSERT OR IGNORE INTO ap_peers (domain, actor_uri, inbox_uri, public_pem, last_seen, created_at)
                         VALUES (?1, ?2, ?3, ?4, ?5, ?5)",
                        rusqlite::params![domain, actor_uri, inbox, key_pem, now],
                    ).ok();

                    // send a Follow to establish mutual connection
                    let follow = serde_json::json!({
                        "@context": "https://www.w3.org/ns/activitystreams",
                        "id": format!("{}/follow/{}", ap.actor_uri, uuid::Uuid::new_v4()),
                        "type": "Follow",
                        "actor": ap.actor_uri,
                        "object": actor_uri,
                    });
                    let payload = serde_json::to_string(&follow).unwrap();
                    dbi.db.execute(
                        "INSERT INTO ap_delivery_queue (inbox_uri, payload, next_retry, created_at) VALUES (?1, ?2, ?3, ?3)",
                        rusqlite::params![inbox, payload, now],
                    ).ok();

                    tracing::info!("discovered cruciverb instance at {} via hashtag search", domain);
                }
            }
        }
    }
}
