//! Mock IDP server implementation for testing.
//!
//! Provides a wiremock-based mock server that simulates the `LastID` IDP
//! for integration testing without network dependencies.

use std::collections::HashMap;
use std::sync::Arc;

use serde_json::json;
use tokio::sync::RwLock;
use wiremock::matchers::{header, method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

use crate::types::RequestId;

/// Mock IDP server for testing credential request flows.
///
/// # Example
///
/// ```rust,no_run
/// use lastid_sdk::test_utils::MockIdpServer;
///
/// # async fn example() -> Result<(), lastid_sdk::LastIDError> {
/// let mock_idp = MockIdpServer::start().await;
///
/// // Configure response for credential request
/// mock_idp.mock_poll_status("req_123", "pending", None).await;
///
/// // Get the base URL for the client
/// let base_url = mock_idp.base_url();
///
/// // Use with LastIDClient configured to point at mock_idp.base_url()
/// # Ok(())
/// # }
/// ```
pub struct MockIdpServer {
    server: MockServer,
    request_states: Arc<RwLock<HashMap<String, String>>>,
}

impl MockIdpServer {
    /// Start a new mock IDP server.
    pub async fn start() -> Self {
        let server = MockServer::start().await;
        let mock_idp = Self {
            server,
            request_states: Arc::new(RwLock::new(HashMap::new())),
        };

        // Set up default endpoints
        mock_idp.setup_default_endpoints().await;

        mock_idp
    }

    /// Get the base URL of the mock server.
    #[must_use]
    pub fn base_url(&self) -> String {
        self.server.uri()
    }

    /// Configure a credential request to return a specific status.
    pub async fn configure_request_response(&self, request_id: &str, req_status: &str) {
        let mut request_map = self.request_states.write().await;
        request_map.insert(request_id.to_string(), req_status.to_string());
    }

    /// Mount a mock for credential request creation.
    pub async fn mock_credential_request(&self, response_request_id: &str) {
        let response = json!({
            "requestId": response_request_id,
            "qrCode": format!("https://lastid.io/request/{response_request_id}"),
            "deepLink": format!("lastid://request/{response_request_id}"),
            "expiresAt": "2026-01-16T12:05:00Z"
        });

        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/request"))
            .and(header("content-type", "application/json"))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(&self.server)
            .await;
    }

    /// Mount a mock for credential request status polling.
    pub async fn mock_poll_status(
        &self,
        request_id: &str,
        status: &str,
        presentation: Option<&str>,
    ) {
        let response = match status {
            "pending" => json!({
                "status": "pending",
                "request_id": request_id,
                "created_at": "2026-01-16T12:00:00Z"
            }),
            "fulfilled" => json!({
                "status": "fulfilled",
                "request_id": request_id,
                "presentation": presentation.unwrap_or("eyJhbGciOiJFUzI1NiJ9.test.sig"),
                "fulfilled_at": "2026-01-16T12:05:00Z"
            }),
            "denied" => json!({
                "status": "denied",
                "request_id": request_id,
                "reason": "User declined",
                "denied_at": "2026-01-16T12:05:00Z"
            }),
            "expired" => json!({
                "status": "expired",
                "request_id": request_id,
                "expired_at": "2026-01-16T12:05:00Z"
            }),
            _ => json!({
                "status": status,
                "request_id": request_id
            }),
        };

        Mock::given(method("GET"))
            .and(path(format!(
                "/api/v1/credentials/request/{request_id}/status"
            )))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(&self.server)
            .await;
    }

    /// Mount a mock for credential verification.
    pub async fn mock_verify_presentation(&self, valid: bool) {
        let response = if valid {
            json!({
                "valid": true,
                "issuer": "did:lastid:verified-issuer",
                "credentialType": "LastID.Base",
                "claims": {
                    "id": "did:lastid:user"
                }
            })
        } else {
            json!({
                "valid": false,
                "error": "Invalid signature"
            })
        };

        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/verify"))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(&self.server)
            .await;
    }

    /// Mount a mock for trust registry issuer lookup.
    pub async fn mock_trust_registry_issuer(
        &self,
        did: &str,
        status: &str,
        permitted_types: Vec<&str>,
    ) {
        let did_suffix = did.split(':').next_back().unwrap_or("test");
        let response = json!({
            "enterpriseDid": did,
            "organizationName": "Test Organization",
            "endpointUrl": format!("https://{did_suffix}.example.com"),
            "status": status,
            "permittedTypes": permitted_types
        });

        let encoded_did = urlencoding::encode(did);
        Mock::given(method("GET"))
            .and(path(format!(
                "/api/v1/trust-registry/issuers/{encoded_did}"
            )))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(&self.server)
            .await;
    }

    /// Mock a 404 response for unknown issuer.
    pub async fn mock_issuer_not_found(&self, did: &str) {
        let encoded_did = urlencoding::encode(did);
        Mock::given(method("GET"))
            .and(path(format!(
                "/api/v1/trust-registry/issuers/{encoded_did}"
            )))
            .respond_with(
                ResponseTemplate::new(404).set_body_json(json!({ "error": "Issuer not found" })),
            )
            .mount(&self.server)
            .await;
    }

    /// Create a builder for expecting credential requests.
    pub fn expect_credential_request(
        &self,
        credential_type: &str,
    ) -> CredentialRequestExpectation<'_> {
        CredentialRequestExpectation {
            server: &self.server,
            credential_type: credential_type.to_string(),
        }
    }

    /// Create a builder for expecting poll requests.
    pub fn expect_poll(&self, request_id: &str) -> PollExpectation<'_> {
        PollExpectation {
            server: &self.server,
            request_id: request_id.to_string(),
        }
    }

    /// Create a builder for expecting verification requests.
    pub const fn expect_verify(&self) -> VerifyExpectation<'_> {
        VerifyExpectation {
            server: &self.server,
        }
    }

    /// Set up default endpoint mocks.
    async fn setup_default_endpoints(&self) {
        let uri = self.server.uri();

        // Health check endpoint
        Mock::given(method("GET"))
            .and(path("/health"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "status": "ok" })))
            .mount(&self.server)
            .await;

        // Discovery endpoint
        Mock::given(method("GET"))
            .and(path("/.well-known/openid-configuration"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "issuer": uri,
                "authorization_endpoint": format!("{uri}/authorize"),
                "token_endpoint": format!("{uri}/token"),
                "jwks_uri": format!("{uri}/.well-known/jwks.json")
            })))
            .mount(&self.server)
            .await;
    }

    /// Get a reference to the underlying `MockServer` for custom
    /// configurations.
    #[must_use]
    pub const fn server(&self) -> &MockServer {
        &self.server
    }

    /// Generate a test request ID.
    #[must_use]
    pub fn generate_request_id() -> RequestId {
        RequestId::new(format!("req_{}", uuid::Uuid::new_v4()))
    }
}

/// Builder for credential request expectations.
pub struct CredentialRequestExpectation<'a> {
    server: &'a MockServer,
    credential_type: String,
}

impl CredentialRequestExpectation<'_> {
    /// Respond with a pending request status.
    pub async fn respond_with_pending(self, request_id: &str) {
        let response = json!({
            "requestId": request_id,
            "status": "pending",
            "credentialType": self.credential_type,
            "createdAt": "2026-01-16T12:00:00Z"
        });

        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/request"))
            .and(header("content-type", "application/json"))
            .respond_with(ResponseTemplate::new(201).set_body_json(response))
            .mount(self.server)
            .await;
    }

    /// Respond with an error.
    pub async fn respond_with_error(self, status_code: u16, error_code: &str, message: &str) {
        let response = json!({
            "error": error_code,
            "message": message
        });

        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/request"))
            .respond_with(ResponseTemplate::new(status_code).set_body_json(response))
            .mount(self.server)
            .await;
    }

    /// Respond with rate limiting (429).
    pub async fn respond_with_rate_limit(self, retry_after_secs: u32) {
        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/request"))
            .respond_with(
                ResponseTemplate::new(429)
                    .append_header("Retry-After", retry_after_secs.to_string().as_str())
                    .set_body_json(json!({
                        "error": "rate_limited",
                        "message": "Too many requests",
                        "retryAfter": retry_after_secs
                    })),
            )
            .mount(self.server)
            .await;
    }
}

/// Builder for poll request expectations.
pub struct PollExpectation<'a> {
    server: &'a MockServer,
    request_id: String,
}

impl PollExpectation<'_> {
    /// Respond with a pending status.
    pub async fn respond_with_pending(self) {
        let request_id = &self.request_id;
        let response = json!({
            "status": "pending",
            "request_id": request_id,
            "created_at": "2026-01-16T12:00:00Z"
        });

        Mock::given(method("GET"))
            .and(path(format!("/api/v1/credentials/request/{request_id}")))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(self.server)
            .await;
    }

    /// Respond with a fulfilled status including the credential.
    pub async fn respond_with_fulfilled(self, credential_jwt: &str) {
        let request_id = &self.request_id;
        let response = json!({
            "status": "fulfilled",
            "request_id": request_id,
            "presentation": credential_jwt,
            "fulfilled_at": "2026-01-16T12:05:00Z"
        });

        Mock::given(method("GET"))
            .and(path(format!("/api/v1/credentials/request/{request_id}")))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(self.server)
            .await;
    }

    /// Respond with a denied status.
    pub async fn respond_with_denied(self, reason: &str) {
        let request_id = &self.request_id;
        let response = json!({
            "status": "denied",
            "request_id": request_id,
            "reason": reason,
            "denied_at": "2026-01-16T12:05:00Z"
        });

        Mock::given(method("GET"))
            .and(path(format!("/api/v1/credentials/request/{request_id}")))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(self.server)
            .await;
    }

    /// Respond with a 404 for unknown request ID.
    pub async fn respond_with_not_found(self) {
        let request_id = &self.request_id;
        Mock::given(method("GET"))
            .and(path(format!("/api/v1/credentials/request/{request_id}")))
            .respond_with(ResponseTemplate::new(404).set_body_json(json!({
                "error": "request_not_found",
                "message": "Credential request not found"
            })))
            .mount(self.server)
            .await;
    }
}

/// Builder for verification request expectations.
pub struct VerifyExpectation<'a> {
    server: &'a MockServer,
}

impl VerifyExpectation<'_> {
    /// Respond with successful verification.
    pub async fn respond_with_valid(self, claims: serde_json::Value) {
        let response = json!({
            "valid": true,
            "claims": claims,
            "issuer": "did:lastid:test-issuer",
            "issuedAt": "2024-01-01T00:00:00Z",
            "expiresAt": "2025-01-01T00:00:00Z"
        });

        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/verify"))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(self.server)
            .await;
    }

    /// Respond with invalid credential.
    pub async fn respond_with_invalid(self, error_code: &str, message: &str) {
        let response = json!({
            "valid": false,
            "error": error_code,
            "message": message
        });

        Mock::given(method("POST"))
            .and(path("/api/v1/credentials/verify"))
            .respond_with(ResponseTemplate::new(200).set_body_json(response))
            .mount(self.server)
            .await;
    }
}

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

    #[tokio::test]
    async fn test_mock_server_starts() {
        let mock_idp = MockIdpServer::start().await;
        assert!(!mock_idp.base_url().is_empty());
        assert!(mock_idp.base_url().starts_with("http://"));
    }

    #[tokio::test]
    async fn test_health_endpoint() {
        let mock_idp = MockIdpServer::start().await;

        let client = reqwest::Client::new();
        let response = client
            .get(format!("{}/health", mock_idp.base_url()))
            .send()
            .await
            .unwrap();

        assert_eq!(response.status(), 200);
    }

    #[tokio::test]
    async fn test_credential_request_mock() {
        let mock_idp = MockIdpServer::start().await;
        mock_idp.mock_credential_request("req_test_123").await;

        let client = reqwest::Client::new();
        let response = client
            .post(format!(
                "{}/api/v1/credentials/request",
                mock_idp.base_url()
            ))
            .header("content-type", "application/json")
            .body("{}")
            .send()
            .await
            .unwrap();

        assert_eq!(response.status(), 200);
        let body: serde_json::Value = response.json().await.unwrap();
        assert_eq!(body["requestId"], "req_test_123");
    }

    #[tokio::test]
    async fn test_poll_status_mock() {
        let mock_idp = MockIdpServer::start().await;
        mock_idp
            .mock_poll_status("req_456", "fulfilled", Some("test-presentation"))
            .await;

        let client = reqwest::Client::new();
        let response = client
            .get(format!(
                "{}/api/v1/credentials/request/req_456/status",
                mock_idp.base_url()
            ))
            .send()
            .await
            .unwrap();

        assert_eq!(response.status(), 200);
        let body: serde_json::Value = response.json().await.unwrap();
        assert_eq!(body["status"], "fulfilled");
        assert_eq!(body["presentation"], "test-presentation");
    }

    #[tokio::test]
    async fn test_credential_request_expectation() {
        let server = MockIdpServer::start().await;
        server
            .expect_credential_request("LastID.Base")
            .respond_with_pending("req-123")
            .await;

        let client = reqwest::Client::new();
        let response = client
            .post(format!("{}/api/v1/credentials/request", server.base_url()))
            .header("content-type", "application/json")
            .json(&json!({"credentialType": "LastID.Base"}))
            .send()
            .await
            .unwrap();

        assert_eq!(response.status(), 201);
    }

    #[tokio::test]
    async fn test_poll_expectation() {
        let server = MockIdpServer::start().await;
        server
            .expect_poll("req-123")
            .respond_with_fulfilled("eyJhbGciOiJFUzI1NiJ9.test.sig")
            .await;

        let client = reqwest::Client::new();
        let response = client
            .get(format!(
                "{}/api/v1/credentials/request/req-123",
                server.base_url()
            ))
            .send()
            .await
            .unwrap();

        assert_eq!(response.status(), 200);
        let body: serde_json::Value = response.json().await.unwrap();
        assert_eq!(body["status"], "fulfilled");
    }
}
