use std::collections::HashMap;

use axum::http::{HeaderMap, HeaderValue, Method, StatusCode};
use axum::response::{IntoResponse, Response};
use mad_hatter::{
    HttpTunnelRequest, HttpTunnelResponse,
    pack_request, unpack_response, unpack_request, pack_response, pack_response_with_limit,
};

// ── pack_request ──

#[test]
fn pack_request_basic() {
    let mut headers = HeaderMap::new();
    headers.insert("content-type", HeaderValue::from_static("application/json"));

    let req = pack_request(&Method::POST, "/api/data", None, &headers, b"hello");

    assert_eq!(req.method, "POST");
    assert_eq!(req.path, "/api/data");
    assert!(req.headers.contains_key("content-type"));
    assert!(req.body.is_some());
}

#[test]
fn pack_request_with_query() {
    let headers = HeaderMap::new();
    let req = pack_request(&Method::GET, "/search", Some("q=test&page=1"), &headers, &[]);

    assert_eq!(req.path, "/search?q=test&page=1");
    assert!(req.body.is_none());
}

#[test]
fn pack_request_empty_query() {
    let headers = HeaderMap::new();
    let req = pack_request(&Method::GET, "/path", Some(""), &headers, &[]);

    assert_eq!(req.path, "/path");
}

#[test]
fn pack_request_no_query() {
    let headers = HeaderMap::new();
    let req = pack_request(&Method::GET, "/path", None, &headers, &[]);

    assert_eq!(req.path, "/path");
}

#[test]
fn pack_request_empty_body() {
    let headers = HeaderMap::new();
    let req = pack_request(&Method::GET, "/", None, &headers, &[]);

    assert!(req.body.is_none());
}

#[test]
fn pack_request_preserves_headers() {
    let mut headers = HeaderMap::new();
    headers.insert("x-custom", HeaderValue::from_static("value1"));
    headers.insert("authorization", HeaderValue::from_static("Bearer token"));

    let req = pack_request(&Method::GET, "/", None, &headers, &[]);

    assert_eq!(req.headers.get("x-custom").unwrap(), "value1");
    assert_eq!(req.headers.get("authorization").unwrap(), "Bearer token");
}

// ── unpack_request ──

#[test]
fn unpack_request_basic() {
    let req = HttpTunnelRequest {
        method: "POST".to_string(),
        path: "/api/data".to_string(),
        headers: HashMap::from([
            ("content-type".to_string(), "application/json".to_string()),
        ]),
        body: Some(base64_encode(b"request body")),
    };

    let (method, path, headers, body) = unpack_request(req);

    assert_eq!(method, Method::POST);
    assert_eq!(path, "/api/data");
    assert_eq!(headers.get("content-type").unwrap(), "application/json");
    assert_eq!(body, b"request body");
}

#[test]
fn unpack_request_invalid_method_defaults_get() {
    let req = HttpTunnelRequest {
        method: "BAD METHOD".to_string(),
        path: "/".to_string(),
        headers: HashMap::new(),
        body: None,
    };

    let (method, _, _, _) = unpack_request(req);
    assert_eq!(method, Method::GET);
}

#[test]
fn unpack_request_no_body() {
    let req = HttpTunnelRequest {
        method: "GET".to_string(),
        path: "/".to_string(),
        headers: HashMap::new(),
        body: None,
    };

    let (_, _, _, body) = unpack_request(req);
    assert!(body.is_empty());
}

#[test]
fn unpack_request_invalid_base64() {
    let req = HttpTunnelRequest {
        method: "GET".to_string(),
        path: "/".to_string(),
        headers: HashMap::new(),
        body: Some("not-valid-base64!!!".to_string()),
    };

    let (_, _, _, body) = unpack_request(req);
    assert!(body.is_empty());
}

// ── unpack_response ──

#[test]
fn unpack_response_basic() {
    let resp = HttpTunnelResponse {
        status: 200,
        headers: HashMap::from([
            ("content-type".to_string(), "text/plain".to_string()),
        ]),
        body: Some(base64_encode(b"ok")),
    };

    let response = unpack_response(resp);

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(response.headers().get("content-type").unwrap(), "text/plain");
}

#[test]
fn unpack_response_invalid_status() {
    let resp = HttpTunnelResponse {
        status: 9999,
        headers: HashMap::new(),
        body: None,
    };

    let response = unpack_response(resp);
    assert_eq!(response.status(), StatusCode::BAD_GATEWAY);
}

#[test]
fn unpack_response_no_body() {
    let resp = HttpTunnelResponse {
        status: 204,
        headers: HashMap::new(),
        body: None,
    };

    let response = unpack_response(resp);
    assert_eq!(response.status(), StatusCode::NO_CONTENT);
}

// ── pack_response ──

#[tokio::test]
async fn pack_response_basic() {
    let response = (StatusCode::OK, "hello world").into_response();
    let packed = pack_response(response).await;

    assert_eq!(packed.status, 200);
    assert!(packed.body.is_some());

    let body = base64_decode(&packed.body.unwrap());
    assert_eq!(body, b"hello world");
}

#[tokio::test]
async fn pack_response_preserves_headers() {
    let mut headers = HeaderMap::new();
    headers.insert("x-custom", HeaderValue::from_static("test-value"));

    let response = (StatusCode::OK, headers, "body").into_response();
    let packed = pack_response(response).await;

    assert_eq!(packed.headers.get("x-custom").unwrap(), "test-value");
}

#[tokio::test]
async fn pack_response_empty_body() {
    let response = StatusCode::NO_CONTENT.into_response();
    let packed = pack_response(response).await;

    assert_eq!(packed.status, 204);
    // Body might be None or empty
}

#[tokio::test]
async fn pack_response_with_limit_truncates() {
    let big_body = vec![0u8; 100];
    let response = (StatusCode::OK, big_body).into_response();
    // Limit to 50 bytes — to_bytes will error, body becomes empty
    let packed = pack_response_with_limit(response, 50).await;

    // When body exceeds limit, to_bytes fails and we get default (empty)
    assert!(packed.body.is_none());
}

// ── roundtrip ──

#[tokio::test]
async fn roundtrip_request() {
    let mut headers = HeaderMap::new();
    headers.insert("content-type", HeaderValue::from_static("application/json"));
    headers.insert("x-test", HeaderValue::from_static("roundtrip"));

    let original_body = b"{\"key\":\"value\"}";

    let packed = pack_request(&Method::PUT, "/api/resource", Some("id=42"), &headers, original_body);
    let (method, path, unpacked_headers, body) = unpack_request(packed);

    assert_eq!(method, Method::PUT);
    assert_eq!(path, "/api/resource?id=42");
    assert_eq!(unpacked_headers.get("content-type").unwrap(), "application/json");
    assert_eq!(unpacked_headers.get("x-test").unwrap(), "roundtrip");
    assert_eq!(body, original_body);
}

#[tokio::test]
async fn roundtrip_response() {
    let mut headers = HeaderMap::new();
    headers.insert("content-type", HeaderValue::from_static("text/html"));

    let original = (StatusCode::CREATED, headers, "<h1>Created</h1>").into_response();
    let packed = pack_response(original).await;

    assert_eq!(packed.status, 201);

    let unpacked = unpack_response(packed);

    assert_eq!(unpacked.status(), StatusCode::CREATED);
    assert_eq!(unpacked.headers().get("content-type").unwrap(), "text/html");

    let body = axum::body::to_bytes(unpacked.into_body(), 1024).await.unwrap();
    assert_eq!(body.as_ref(), b"<h1>Created</h1>");
}

#[tokio::test]
async fn roundtrip_empty_request_response() {
    // Empty GET request
    let packed_req = pack_request(&Method::GET, "/health", None, &HeaderMap::new(), &[]);
    let (method, path, _, body) = unpack_request(packed_req);
    assert_eq!(method, Method::GET);
    assert_eq!(path, "/health");
    assert!(body.is_empty());

    // Empty 204 response
    let resp = StatusCode::NO_CONTENT.into_response();
    let packed_resp = pack_response(resp).await;
    assert_eq!(packed_resp.status, 204);

    let unpacked = unpack_response(packed_resp);
    assert_eq!(unpacked.status(), StatusCode::NO_CONTENT);
}

// ── serialization ──

#[test]
fn request_serializes_to_json() {
    let req = HttpTunnelRequest {
        method: "GET".to_string(),
        path: "/".to_string(),
        headers: HashMap::new(),
        body: None,
    };

    let json = serde_json::to_string(&req).unwrap();
    let deserialized: HttpTunnelRequest = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.method, "GET");
    assert_eq!(deserialized.path, "/");
}

#[test]
fn response_serializes_to_json() {
    let resp = HttpTunnelResponse {
        status: 200,
        headers: HashMap::from([("k".to_string(), "v".to_string())]),
        body: Some("dGVzdA==".to_string()),
    };

    let json = serde_json::to_string(&resp).unwrap();
    let deserialized: HttpTunnelResponse = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.status, 200);
    assert_eq!(deserialized.body.unwrap(), "dGVzdA==");
}

// ── helpers ──

fn base64_encode(data: &[u8]) -> String {
    use base64::Engine as _;
    base64::engine::general_purpose::STANDARD.encode(data)
}

fn base64_decode(data: &str) -> Vec<u8> {
    use base64::Engine as _;
    base64::engine::general_purpose::STANDARD.decode(data).unwrap()
}