use std::time::Duration;

use serde_json::json;
use serial_test::serial;
use sumup::{version, Authorization, Client};
use wiremock::matchers::{header, method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

struct EnvVarGuard {
    key: &'static str,
    original: Option<String>,
}

impl EnvVarGuard {
    fn set(key: &'static str, value: &str) -> Self {
        let original = std::env::var(key).ok();
        std::env::set_var(key, value);
        Self { key, original }
    }

    fn unset(key: &'static str) -> Self {
        let original = std::env::var(key).ok();
        std::env::remove_var(key);
        Self { key, original }
    }
}

impl Drop for EnvVarGuard {
    fn drop(&mut self) {
        if let Some(ref value) = self.original {
            std::env::set_var(self.key, value);
        } else {
            std::env::remove_var(self.key);
        }
    }
}

#[test]
fn client_uses_default_base_url() {
    let client = Client::default();
    assert_eq!(client.base_url(), "https://api.sumup.com");
}

#[test]
fn client_with_base_url_overrides_default() {
    let client = Client::new().with_base_url("https://mock.sumup.internal.test");
    assert_eq!(client.base_url(), "https://mock.sumup.internal.test");
}

#[test]
fn client_with_timeout_updates_timeout() {
    let timeout = Duration::from_secs(42);
    let client = Client::new().with_timeout(timeout);
    assert_eq!(client.timeout(), timeout);
}

#[test]
#[serial]
fn client_reads_authorization_from_env() {
    let token = "env-token";
    let _guard = EnvVarGuard::set("SUMUP_API_KEY", token);

    let client = Client::new();

    assert_eq!(client.authorization(), Some(token));
}

#[test]
#[serial]
fn client_with_authorization_overrides_env_value() {
    let _guard = EnvVarGuard::set("SUMUP_API_KEY", "env-token");
    let override_token = "override-token";

    let client = Client::new().with_authorization(Authorization::api_key(override_token));

    assert_eq!(client.authorization(), Some(override_token));
}

#[tokio::test]
#[serial]
async fn client_requests_include_user_agent_and_custom_authorization() {
    let server = MockServer::start().await;
    let _guard = EnvVarGuard::set("SUMUP_API_KEY", "env-token");
    let override_token = "override-token";
    let expected_auth = format!("Bearer {}", override_token);
    let expected_user_agent = version::user_agent();

    let _mock = Mock::given(method("GET"))
        .and(path("/v0.1/checkouts"))
        .and(header("User-Agent", expected_user_agent.as_str()))
        .and(header("Authorization", expected_auth.as_str()))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
        .expect(1)
        .mount_as_scoped(&server)
        .await;

    let client = Client::new()
        .with_base_url(server.uri())
        .with_authorization(Authorization::api_key(override_token));

    client
        .checkouts()
        .list(sumup::resources::checkouts::ListParams::default())
        .await
        .expect("request should succeed");
}

#[tokio::test]
#[serial]
async fn client_requests_include_runtime_headers() {
    let server = MockServer::start().await;
    let _guard = EnvVarGuard::set("SUMUP_API_KEY", "env-token");
    let expected_user_agent = version::user_agent();

    let mut mock = Mock::given(method("GET"))
        .and(path("/v0.1/checkouts"))
        .and(header("User-Agent", expected_user_agent.as_str()));

    for (header_name, header_value) in version::runtime_info() {
        mock = mock.and(header(header_name, header_value.as_str()));
    }

    let _mock = mock
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
        .expect(1)
        .mount_as_scoped(&server)
        .await;

    let client = Client::new().with_base_url(server.uri());

    client
        .checkouts()
        .list(sumup::resources::checkouts::ListParams::default())
        .await
        .expect("request should succeed");
}

#[test]
#[serial]
fn client_returns_none_when_authorization_missing() {
    let _guard = EnvVarGuard::unset("SUMUP_API_KEY");
    let client = Client::new();
    assert!(client.authorization().is_none());
}
