use crate::error::{EgregoreError, Result};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AuthorizationServerMetadata {
    pub issuer: String,
    pub authorization_endpoint: String,
    pub token_endpoint: String,
    pub jwks_uri: String,
    pub pushed_authorization_request_endpoint: Option<String>,
    pub introspection_endpoint: Option<String>,
    pub revocation_endpoint: Option<String>,
}

#[derive(Debug, Clone)]
pub struct DiscoveryResult {
    pub did: String,
    pub pds_url: String,
    pub metadata: AuthorizationServerMetadata,
}

pub struct Discovery {
    http: reqwest::Client,
}

impl Discovery {
    pub fn new() -> Self {
        Self {
            http: reqwest::Client::new(),
        }
    }

    pub async fn resolve_handle(&self, handle: &str) -> Result<String> {
        // 1. Try HTTPS well-known first
        let url = format!("https://{}/.well-known/atproto-did", handle);
        if let Ok(resp) = self.http.get(&url).send().await {
            if resp.status().is_success() {
                let did = resp.text().await?.trim().to_string();
                if did.starts_with("did:") {
                    return Ok(did);
                }
            }
        }

        // 2. Fall back to PDS-based resolution (using a public PDS like bsky.social)
        let url = format!(
            "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle={}",
            handle
        );
        if let Ok(resp) = self.http.get(&url).send().await {
            if resp.status().is_success() {
                let data: serde_json::Value = resp.json().await?;
                if let Some(did) = data["did"].as_str() {
                    return Ok(did.to_string());
                }
            }
        }

        Err(EgregoreError::Other(format!(
            "Could not resolve handle: {}",
            handle
        )))
    }

    pub async fn resolve_did_to_pds(&self, did: &str) -> Result<String> {
        let url = if did.starts_with("did:plc:") {
            format!("https://plc.directory/{}", did)
        } else if let Some(domain) = did.strip_prefix("did:web:") {
            format!("https://{}/.well-known/did.json", domain)
        } else {
            return Err(EgregoreError::Other(format!(
                "Unsupported DID method: {}",
                did
            )));
        };

        let resp = self.http.get(&url).send().await?;
        if !resp.status().is_success() {
            return Err(EgregoreError::Other(format!(
                "Failed to fetch DID document for {}",
                did
            )));
        }
        let doc: serde_json::Value = resp.json().await?;

        doc["service"]
            .as_array()
            .and_then(|services| {
                services.iter().find(|s| {
                    let id = s["id"].as_str().unwrap_or("");
                    id == "#atproto_pds" || id.ends_with("#atproto_pds")
                })
            })
            .and_then(|s| s["serviceEndpoint"].as_str())
            .map(|s| s.to_string())
            .ok_or_else(|| {
                EgregoreError::Other(format!("No PDS endpoint found in DID document for {}", did))
            })
    }

    pub async fn fetch_auth_server_metadata(
        &self,
        pds_url: &str,
    ) -> Result<AuthorizationServerMetadata> {
        // Try the PDS url first
        let url = format!(
            "{}/.well-known/oauth-authorization-server",
            pds_url.trim_end_matches('/')
        );
        let resp = self.http.get(&url).send().await?;

        if resp.status().is_success() {
            return Ok(resp.json().await?);
        }

        // If PDS doesn't have it, and it looks like a Bluesky host, use bsky.social
        if pds_url.contains(".host.bsky.network") || pds_url.contains(".bsky.social") {
            let fallback_url = "https://bsky.social/.well-known/oauth-authorization-server";
            let resp = self.http.get(fallback_url).send().await?;
            if resp.status().is_success() {
                return Ok(resp.json().await?);
            }
        }

        Err(EgregoreError::Other(format!(
            "Failed to find AS metadata for PDS {}",
            pds_url
        )))
    }

    pub async fn discover(&self, handle: &str) -> Result<DiscoveryResult> {
        let did = self.resolve_handle(handle).await?;
        let pds_url = self.resolve_did_to_pds(&did).await?;
        let metadata = self.fetch_auth_server_metadata(&pds_url).await?;
        Ok(DiscoveryResult {
            did,
            pds_url,
            metadata,
        })
    }
}

impl Default for Discovery {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[tokio::test]
    async fn test_resolve_known_handle() {
        let discovery = Discovery::new();
        let did = discovery.resolve_handle("bsky.app").await.unwrap();
        assert!(did.starts_with("did:plc:"));
    }

    #[tokio::test]
    async fn test_resolve_did_to_pds() {
        let discovery = Discovery::new();
        // bsky.app's DID — look it up once and hardcode
        let pds = discovery
            .resolve_did_to_pds("did:plc:z72i7hdynmk6r22z27h6tvur")
            .await
            .unwrap();
        assert!(pds.starts_with("https://"));
    }

    #[tokio::test]
    async fn test_full_discovery() {
        let discovery = Discovery::new();
        let result = discovery.discover("bsky.app").await.unwrap();
        assert!(result.did.starts_with("did:plc:"));
        assert!(!result.pds_url.is_empty());
        assert!(!result.metadata.authorization_endpoint.is_empty());
    }

    #[tokio::test]
    async fn test_resolve_nonexistent_handle() {
        let discovery = Discovery::new();
        let result = discovery
            .resolve_handle("this-handle-does-not-exist-12345.bsky.social")
            .await;
        assert!(result.is_err());
    }
}
