use std::sync::Arc;
use axum::Json;
use axum::extract::{Path, Query, State};
use axum::http::header;
use axum::response::{IntoResponse, Response};

use crate::db;
use crate::state::AppState;
use super::types::*;

#[derive(serde::Deserialize)]
pub struct WebFingerQuery {
    resource: String,
}

pub async fn webfinger(
    State(state): State<Arc<AppState>>,
    Query(q): Query<WebFingerQuery>,
) -> Result<Json<WebFingerResponse>, Response> {
    let ap = state.ap.as_ref().ok_or_else(|| (axum::http::StatusCode::NOT_FOUND, "not found").into_response())?;
    let expected = format!("acct:{}@{}", ap.actor_name, ap.domain);

    // also match group actors like acct:cruciverb-animals@domain
    let prefix = format!("acct:{}-", ap.actor_name);
    if q.resource.starts_with(&prefix) && q.resource.contains(&format!("@{}", ap.domain)) {
        let name = q.resource.strip_prefix("acct:").unwrap_or("").split('@').next().unwrap_or("");
        let uri = format!("https://{}/@{}", ap.domain, name);
        return Ok(Json(WebFingerResponse {
            subject: q.resource.clone(),
            links: vec![WebFingerLink {
                rel: "self".into(),
                link_type: Some("application/activity+json".into()),
                href: uri,
            }],
        }));
    }

    if q.resource != expected {
        return Err((axum::http::StatusCode::NOT_FOUND, "not found").into_response());
    }
    Ok(Json(WebFingerResponse {
        subject: expected,
        links: vec![WebFingerLink {
            rel: "self".into(),
            link_type: Some("application/activity+json".into()),
            href: ap.actor_uri.clone(),
        }],
    }))
}

pub async fn actor_document(
    State(state): State<Arc<AppState>>,
    Path(name): Path<String>,
) -> Result<Response, Response> {
    let ap = state.ap.as_ref().ok_or_else(|| (axum::http::StatusCode::NOT_FOUND, "not found").into_response())?;

    // check if this is a group actor request (e.g. @cruciverb-animals)
    let prefix = format!("{}-", ap.actor_name);
    if name.starts_with(&prefix) && name.len() > prefix.len() {
        let theme = &name[prefix.len()..];
        return group_actor_document(ap, theme).await;
    }

    // only respond for the configured actor name
    if name != ap.actor_name {
        return Err((axum::http::StatusCode::NOT_FOUND, "not found").into_response());
    }

    let default_bio = "Daily crossword puzzles in five sizes, four game modes, cryptic clues, multiplayer rooms, party games, a built-in puzzle editor for constructors, and a clue trainer for the stubborn. Self-hosted on whatever you call a server. Federated via ActivityPub because crosswords are social infrastructure. CC0 because charging for grid squares is morally dubious.";

    let (display_name, bio, avatar_path, banner_path) = {
        let dbi = state.db.lock().await;
        let dn = db::get_server_config(&dbi.db, "ap_actor_name")
            .unwrap_or_else(|| std::env::var("AP_ACTOR_NAME").unwrap_or_else(|_| "crossword".into()));
        let b = db::get_server_config(&dbi.db, "ap_bio")
            .unwrap_or_else(|| default_bio.into());
        let av = db::get_server_config(&dbi.db, "ap_avatar_path");
        let bn = db::get_server_config(&dbi.db, "ap_banner_path");
        (dn, b, av, bn)
    };

    let mut doc = serde_json::json!({
        "@context": [CONTEXT, SECURITY_CONTEXT],
        "id": ap.actor_uri,
        "type": "Application",
        "preferredUsername": ap.actor_name,
        "name": display_name,
        "summary": bio,
        "inbox": format!("{}/inbox", ap.actor_uri),
        "outbox": format!("{}/outbox", ap.actor_uri),
        "followers": format!("{}/followers", ap.actor_uri),
        "publicKey": {
            "id": format!("{}#main-key", ap.actor_uri),
            "owner": ap.actor_uri,
            "publicKeyPem": ap.keys.public_key_pem,
        },
        "endpoints": {
            "sharedInbox": format!("https://{}/inbox", ap.domain),
        },
    });

    if let Some(ref av) = avatar_path {
        let url = format!("https://{}/avatar", ap.domain);
        doc["icon"] = serde_json::json!({
            "type": "Image",
            "mediaType": guess_media_type(av),
            "url": url,
        });
    }
    if let Some(ref bn) = banner_path {
        let url = format!("https://{}/banner", ap.domain);
        doc["image"] = serde_json::json!({
            "type": "Image",
            "mediaType": guess_media_type(bn),
            "url": url,
        });
    }

    let body = serde_json::to_string(&doc).unwrap();
    Ok((
        [(header::CONTENT_TYPE, "application/activity+json; charset=utf-8")],
        body,
    ).into_response())
}

async fn group_actor_document(ap: &crate::activitypub::ApState, theme: &str) -> Result<Response, Response> {
    let group_name = format!("{}-{}", ap.actor_name, theme);
    let group_uri = format!("https://{}/@{}", ap.domain, group_name);

    let actor = Actor {
        context: serde_json::json!([CONTEXT, SECURITY_CONTEXT]),
        id: group_uri.clone(),
        actor_type: "Group".into(),
        preferred_username: group_name.clone(),
        name: format!("cruciverb {} puzzles", theme),
        summary: format!("themed crossword collection - {}", theme),
        inbox: format!("{}/inbox", group_uri),
        outbox: format!("{}/outbox", group_uri),
        followers: format!("{}/followers", group_uri),
        public_key: ActorPublicKey {
            id: format!("{}#main-key", group_uri),
            owner: group_uri.clone(),
            public_key_pem: ap.keys.public_key_pem.clone(),
        },
        endpoints: Some(serde_json::json!({
            "sharedInbox": format!("https://{}/inbox", ap.domain),
        })),
    };

    let body = serde_json::to_string(&actor).unwrap();
    Ok((
        [(header::CONTENT_TYPE, "application/activity+json; charset=utf-8")],
        body,
    ).into_response())
}

fn guess_media_type(url: &str) -> &'static str {
    let lower = url.to_lowercase();
    if lower.ends_with(".png") { "image/png" }
    else if lower.ends_with(".gif") { "image/gif" }
    else if lower.ends_with(".webp") { "image/webp" }
    else if lower.ends_with(".svg") { "image/svg+xml" }
    else { "image/jpeg" }
}

pub async fn followers(
    State(state): State<Arc<AppState>>,
) -> Result<Json<OrderedCollection>, Response> {
    let ap = state.ap.as_ref().ok_or_else(|| (axum::http::StatusCode::NOT_FOUND, "not found").into_response())?;
    let dbi = state.db.lock().await;
    let mut stmt = dbi.db.prepare(
        "SELECT actor_uri FROM ap_followers WHERE accepted = 1"
    ).unwrap();
    let items: Vec<serde_json::Value> = stmt.query_map([], |row| {
        Ok(serde_json::json!(row.get::<_, String>(0)?))
    }).unwrap().filter_map(|r| r.ok()).collect();

    Ok(Json(OrderedCollection {
        context: CONTEXT.into(),
        id: format!("{}/followers", ap.actor_uri),
        collection_type: "OrderedCollection".into(),
        total_items: items.len(),
        ordered_items: items,
    }))
}
