use edgar_rs::{Cik, ClientBuilder, Error, SearchOptions};
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};

/// Helper: build a client pointing at the given mock server.
fn test_client(server: &MockServer) -> edgar_rs::Client {
    let base = server.uri();
    ClientBuilder::new("TestApp/1.0 test@example.com")
        .rate_limit(100) // high limit so tests aren't throttled
        .base_sec_url(&base)
        .base_data_url(&base)
        .base_efts_url(&base)
        .build()
        .unwrap()
}

// ─── ClientBuilder tests ───────────────────────────────

#[test]
fn builder_rejects_empty_user_agent() {
    let err = ClientBuilder::new("").build().unwrap_err();
    assert!(matches!(err, Error::Config(_)));
    assert!(err.to_string().contains("user-agent"));
}

#[test]
fn builder_rejects_zero_rate_limit() {
    let err = ClientBuilder::new("Test/1.0 test@test.com")
        .rate_limit(0)
        .build()
        .unwrap_err();
    assert!(matches!(err, Error::Config(_)));
    assert!(err.to_string().contains("rate limit"));
}

#[test]
fn builder_accepts_valid_config() {
    let client = ClientBuilder::new("Test/1.0 test@test.com").build();
    assert!(client.is_ok());
}

#[test]
fn builder_accepts_custom_rate_limit() {
    let client = ClientBuilder::new("Test/1.0 test@test.com")
        .rate_limit(5)
        .build();
    assert!(client.is_ok());
}

#[test]
fn builder_accepts_custom_http_client() {
    let http = reqwest::Client::new();
    let client = ClientBuilder::new("Test/1.0 test@test.com")
        .http_client(http)
        .build();
    assert!(client.is_ok());
}

// ─── GetTickers ────────────────────────────────────────

#[tokio::test]
async fn get_tickers_success() {
    let server = MockServer::start().await;
    let body = r#"{
        "0": {"cik_str": 320193, "ticker": "AAPL", "title": "Apple Inc."},
        "1": {"cik_str": 789019, "ticker": "MSFT", "title": "MICROSOFT CORP"}
    }"#;

    Mock::given(method("GET"))
        .and(path("/files/company_tickers.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let tickers = client.get_tickers().await.unwrap();

    assert_eq!(tickers.len(), 2);
    assert_eq!(tickers["320193"].ticker, "AAPL");
    assert_eq!(tickers["789019"].ticker, "MSFT");
}

#[tokio::test]
async fn get_tickers_rekeys_by_cik() {
    let server = MockServer::start().await;
    let body = r#"{
        "0": {"cik_str": 12345, "ticker": "FOO", "title": "Foo Corp"}
    }"#;

    Mock::given(method("GET"))
        .and(path("/files/company_tickers.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let tickers = client.get_tickers().await.unwrap();

    // Should be keyed by CIK string, not the original "0" key
    assert!(tickers.contains_key("12345"));
    assert!(!tickers.contains_key("0"));
}

// ─── GetSubmission ─────────────────────────────────────

#[tokio::test]
async fn get_submission_success() {
    let server = MockServer::start().await;
    let body = r#"{
        "cik": "320193",
        "entityType": "operating",
        "sic": "3571",
        "sicDescription": "ELECTRONIC COMPUTERS",
        "insiderTransactionForOwnerExists": 1,
        "insiderTransactionForIssuerExists": 0,
        "name": "Apple Inc.",
        "tickers": ["AAPL"],
        "exchanges": ["Nasdaq"],
        "ein": "942404110",
        "description": "",
        "website": "https://www.apple.com",
        "investorWebsite": "",
        "category": "Large accelerated filer",
        "fiscalYearEnd": "0930",
        "stateOfIncorporation": "CA",
        "stateOfIncorporationDescription": "CA",
        "addresses": {
            "mailing": {
                "street1": "ONE APPLE PARK WAY",
                "street2": null,
                "city": "CUPERTINO",
                "stateOrCountry": "CA",
                "zipCode": "95014",
                "stateOrCountryDescription": "CA"
            },
            "business": {
                "street1": "ONE APPLE PARK WAY",
                "street2": null,
                "city": "CUPERTINO",
                "stateOrCountry": "CA",
                "zipCode": "95014",
                "stateOrCountryDescription": "CA"
            }
        },
        "phone": "408-996-1010",
        "flags": "",
        "formerNames": [
            {"name": "APPLE COMPUTER INC", "from": "1977-01-03", "to": "2007-01-09"}
        ],
        "filings": {
            "recent": {
                "accessionNumber": ["0000320193-24-000123"],
                "filingDate": ["2024-01-15"],
                "reportDate": ["2024-01-15"],
                "acceptanceDateTime": ["2024-01-15T16:30:00.000Z"],
                "act": ["34"],
                "form": ["10-K"],
                "fileNumber": ["001-36743"],
                "filmNumber": [""],
                "items": [""],
                "size": [12345678],
                "isXBRL": [1],
                "isInlineXBRL": [1],
                "primaryDocument": ["aapl-20240101.htm"],
                "primaryDocDescription": ["10-K"]
            },
            "files": [
                {
                    "name": "CIK0000320193-submissions-001.json",
                    "filingCount": 40,
                    "filingFrom": "2020-01-01",
                    "filingTo": "2023-12-31"
                }
            ]
        }
    }"#;

    Mock::given(method("GET"))
        .and(path("/submissions/CIK0000320193.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let sub = client.get_submission(Cik::new(320193)).await.unwrap();

    assert_eq!(sub.cik, "320193");
    assert_eq!(sub.name, "Apple Inc.");
    assert_eq!(sub.sic, "3571");
    assert_eq!(sub.tickers, vec!["AAPL"]);
    assert_eq!(sub.former_names.len(), 1);
    assert_eq!(sub.former_names[0].name, "APPLE COMPUTER INC");
    assert_eq!(sub.filings.recent.accession_number.len(), 1);
    assert_eq!(sub.filings.files.len(), 1);
}

#[tokio::test]
async fn get_submission_zero_pads_cik() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/submissions/CIK0000000001.json"))
        .respond_with(ResponseTemplate::new(404))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let err = client.get_submission(Cik::new(1)).await.unwrap_err();
    assert!(err.is_not_found());
}

// ─── GetDocument ───────────────────────────────────────

#[tokio::test]
async fn get_document_success() {
    let server = MockServer::start().await;
    let html = "<html><body>Filing content</body></html>";

    Mock::given(method("GET"))
        .and(path(
            "/Archives/edgar/data/0000320193/000032019324000123/doc.htm",
        ))
        .respond_with(ResponseTemplate::new(200).set_body_string(html))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let doc = client
        .get_document(Cik::new(320193), "0000320193-24-000123", "doc.htm")
        .await
        .unwrap();

    assert_eq!(doc, html);
}

#[tokio::test]
async fn get_document_strips_dashes_from_accession() {
    let server = MockServer::start().await;

    // Accession "0000320193-24-000123" should become "000032019324000123" in URL
    Mock::given(method("GET"))
        .and(path(
            "/Archives/edgar/data/0000320193/000032019324000123/doc.htm",
        ))
        .respond_with(ResponseTemplate::new(200).set_body_string("ok"))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let result = client
        .get_document(Cik::new(320193), "0000320193-24-000123", "doc.htm")
        .await;
    assert!(result.is_ok());
}

// ─── GetCompanyConcept ─────────────────────────────────

#[tokio::test]
async fn get_company_concept_success() {
    let server = MockServer::start().await;
    let body = r#"{
        "cik": 320193,
        "taxonomy": "us-gaap",
        "tag": "Revenues",
        "label": "Revenues",
        "description": "Total revenues",
        "entityName": "Apple Inc.",
        "units": {
            "USD": [
                {
                    "start": "2023-01-01",
                    "end": "2023-12-31",
                    "val": 394328000000.0,
                    "accn": "0000320193-24-000123",
                    "fy": 2023,
                    "fp": "FY",
                    "form": "10-K",
                    "filed": "2024-01-15",
                    "frame": "CY2023"
                }
            ]
        }
    }"#;

    Mock::given(method("GET"))
        .and(path(
            "/api/xbrl/companyconcept/CIK0000320193/us-gaap/Revenues.json",
        ))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let concept = client
        .get_company_concept(Cik::new(320193), "us-gaap", "Revenues")
        .await
        .unwrap();

    assert_eq!(concept.cik, 320193);
    assert_eq!(concept.taxonomy, "us-gaap");
    assert_eq!(concept.tag, "Revenues");
    assert_eq!(concept.entity_name, "Apple Inc.");
    let facts = &concept.units["USD"];
    assert_eq!(facts.len(), 1);
    assert_eq!(facts[0].val, 394328000000.0);
    assert_eq!(facts[0].frame.as_deref(), Some("CY2023"));
}

// ─── GetCompanyFacts ───────────────────────────────────

#[tokio::test]
async fn get_company_facts_success() {
    let server = MockServer::start().await;
    let body = r#"{
        "cik": 320193,
        "entityName": "Apple Inc.",
        "facts": {
            "us-gaap": {
                "Revenues": {
                    "label": "Revenues",
                    "description": "Total revenues",
                    "units": {
                        "USD": [
                            {
                                "end": "2023-12-31",
                                "val": 394328000000.0,
                                "accn": "0000320193-24-000123",
                                "fy": 2023,
                                "fp": "FY",
                                "form": "10-K",
                                "filed": "2024-01-15"
                            }
                        ]
                    }
                }
            },
            "dei": {
                "EntityCommonStockSharesOutstanding": {
                    "label": "Shares Outstanding",
                    "description": "Common shares outstanding",
                    "units": {
                        "shares": [
                            {
                                "end": "2023-12-31",
                                "val": 15550061000.0,
                                "accn": "0000320193-24-000123",
                                "fy": 2023,
                                "fp": "FY",
                                "form": "10-K",
                                "filed": "2024-01-15"
                            }
                        ]
                    }
                }
            }
        }
    }"#;

    Mock::given(method("GET"))
        .and(path("/api/xbrl/companyfacts/CIK0000320193.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let facts = client.get_company_facts(Cik::new(320193)).await.unwrap();

    assert_eq!(facts.cik, 320193);
    assert_eq!(facts.entity_name, "Apple Inc.");
    assert!(facts.facts.contains_key("us-gaap"));
    assert!(facts.facts.contains_key("dei"));
    let revenues = &facts.facts["us-gaap"]["Revenues"];
    assert_eq!(revenues.label, "Revenues");
    assert_eq!(revenues.units["USD"][0].val, 394328000000.0);
}

// ─── GetFrame ──────────────────────────────────────────

#[tokio::test]
async fn get_frame_success() {
    let server = MockServer::start().await;
    let body = r#"{
        "taxonomy": "us-gaap",
        "tag": "Revenues",
        "ccp": "CY2023",
        "uom": "USD",
        "label": "Revenues",
        "description": "Total revenues",
        "pts": 2,
        "data": [
            {
                "accn": "0000320193-24-000123",
                "cik": 320193,
                "entityName": "Apple Inc.",
                "loc": "US-CA",
                "start": "2023-01-01",
                "end": "2023-12-31",
                "val": 394328000000.0
            },
            {
                "accn": "0000789019-24-000456",
                "cik": 789019,
                "entityName": "MICROSOFT CORP",
                "loc": "US-WA",
                "end": "2023-12-31",
                "val": 211915000000.0
            }
        ]
    }"#;

    Mock::given(method("GET"))
        .and(path("/api/xbrl/frames/us-gaap/Revenues/USD/CY2023.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let frame = client
        .get_frame("us-gaap", "Revenues", "USD", "CY2023")
        .await
        .unwrap();

    assert_eq!(frame.taxonomy, "us-gaap");
    assert_eq!(frame.ccp, "CY2023");
    assert_eq!(frame.pts, 2);
    assert_eq!(frame.data.len(), 2);
    assert_eq!(frame.data[0].cik, 320193);
    assert_eq!(frame.data[0].start.as_deref(), Some("2023-01-01"));
    assert!(frame.data[1].start.is_none());
}

#[tokio::test]
async fn get_frame_quarterly_instant() {
    let server = MockServer::start().await;
    let body = r#"{
        "taxonomy": "us-gaap",
        "tag": "Assets",
        "ccp": "CY2023Q1I",
        "uom": "USD",
        "label": "Assets",
        "description": "Total assets",
        "pts": 1,
        "data": [
            {
                "accn": "0000320193-24-000001",
                "cik": 320193,
                "entityName": "Apple Inc.",
                "loc": "US-CA",
                "end": "2023-03-31",
                "val": 346747000000.0
            }
        ]
    }"#;

    Mock::given(method("GET"))
        .and(path("/api/xbrl/frames/us-gaap/Assets/USD/CY2023Q1I.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let frame = client
        .get_frame("us-gaap", "Assets", "USD", "CY2023Q1I")
        .await
        .unwrap();

    assert_eq!(frame.ccp, "CY2023Q1I");
    assert_eq!(frame.data.len(), 1);
}

// ─── Search ────────────────────────────────────────────

#[tokio::test]
async fn search_basic_query() {
    let server = MockServer::start().await;
    let body = r#"{
        "hits": {
            "total": {"value": 1},
            "hits": [
                {
                    "_id": "doc1",
                    "_score": 15.5,
                    "_source": {
                        "ciks": ["320193"],
                        "display_names": ["Apple Inc. (AAPL)"],
                        "form": "10-K",
                        "file_date": "2024-01-15",
                        "period_ending": "2023-12-31",
                        "adsh": "0000320193-24-000123",
                        "file_type": "10-K",
                        "file_description": "Annual Report"
                    }
                }
            ]
        }
    }"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .and(query_param("q", "Apple"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let result = client.search("Apple", None).await.unwrap();

    assert_eq!(result.total, 1);
    assert_eq!(result.hits.len(), 1);
    assert_eq!(result.hits[0].id, "doc1");
    assert_eq!(result.hits[0].score, 15.5);
    assert_eq!(result.hits[0].ciks, vec!["320193"]);
    assert_eq!(result.hits[0].form, "10-K");
    assert_eq!(result.hits[0].accession_number, "0000320193-24-000123");
    assert_eq!(result.hits[0].period_ending.as_deref(), Some("2023-12-31"));
}

#[tokio::test]
async fn search_with_form_filter() {
    let server = MockServer::start().await;
    let body = r#"{"hits": {"total": {"value": 0}, "hits": []}}"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .and(query_param("q", "test"))
        .and(query_param("forms", "10-K,10-Q"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let opts = SearchOptions {
        forms: vec!["10-K".into(), "10-Q".into()],
        ..Default::default()
    };
    let result = client.search("test", Some(&opts)).await.unwrap();
    assert_eq!(result.total, 0);
}

#[tokio::test]
async fn search_with_date_range() {
    let server = MockServer::start().await;
    let body = r#"{"hits": {"total": {"value": 0}, "hits": []}}"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .and(query_param("q", "test"))
        .and(query_param("dateRange", "custom"))
        .and(query_param("startdt", "2023-01-01"))
        .and(query_param("enddt", "2023-12-31"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let opts = SearchOptions {
        date_start: Some("2023-01-01".into()),
        date_end: Some("2023-12-31".into()),
        ..Default::default()
    };
    let result = client.search("test", Some(&opts)).await.unwrap();
    assert_eq!(result.total, 0);
}

#[tokio::test]
async fn search_with_pagination() {
    let server = MockServer::start().await;
    let body = r#"{"hits": {"total": {"value": 0}, "hits": []}}"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .and(query_param("q", "test"))
        .and(query_param("from", "100"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let opts = SearchOptions {
        from: Some(100),
        ..Default::default()
    };
    let result = client.search("test", Some(&opts)).await.unwrap();
    assert_eq!(result.total, 0);
}

#[tokio::test]
async fn search_without_period_ending() {
    let server = MockServer::start().await;
    let body = r#"{
        "hits": {
            "total": {"value": 1},
            "hits": [
                {
                    "_id": "doc1",
                    "_score": 10.0,
                    "_source": {
                        "ciks": ["320193"],
                        "display_names": ["Apple Inc."],
                        "form": "8-K",
                        "file_date": "2024-01-15",
                        "adsh": "0000320193-24-000123",
                        "file_type": "8-K",
                        "file_description": "Current Report"
                    }
                }
            ]
        }
    }"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let result = client.search("Apple", None).await.unwrap();
    assert!(result.hits[0].period_ending.is_none());
}

#[tokio::test]
async fn search_from_zero_is_not_sent() {
    let server = MockServer::start().await;
    let body = r#"{"hits": {"total": {"value": 0}, "hits": []}}"#;

    // Mount with NO "from" param expected
    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .and(query_param("q", "test"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .expect(1)
        .mount(&server)
        .await;

    let client = test_client(&server);
    let opts = SearchOptions {
        from: Some(0), // 0 should NOT be sent as a param
        ..Default::default()
    };
    let result = client.search("test", Some(&opts)).await.unwrap();
    assert_eq!(result.total, 0);
}

// ─── Error handling ────────────────────────────────────

#[tokio::test]
async fn returns_api_error_on_404() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/submissions/CIK0000000001.json"))
        .respond_with(ResponseTemplate::new(404))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let err = client.get_submission(Cik::new(1)).await.unwrap_err();

    assert!(err.is_not_found());
    assert!(!err.is_rate_limited());
    assert!(matches!(err, Error::Api { .. }));
}

#[tokio::test]
async fn returns_api_error_on_429() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/files/company_tickers.json"))
        .respond_with(ResponseTemplate::new(429))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let err = client.get_tickers().await.unwrap_err();

    assert!(err.is_rate_limited());
    assert!(!err.is_not_found());
}

#[tokio::test]
async fn returns_api_error_on_500() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/files/company_tickers.json"))
        .respond_with(ResponseTemplate::new(500))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let err = client.get_tickers().await.unwrap_err();

    assert!(!err.is_rate_limited());
    assert!(!err.is_not_found());
    assert!(matches!(err, Error::Api { .. }));
}

#[tokio::test]
async fn returns_decode_error_on_invalid_json() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/files/company_tickers.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string("not json"))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let err = client.get_tickers().await.unwrap_err();

    assert!(matches!(err, Error::Decode { .. }));
}

#[tokio::test]
async fn returns_request_error_on_connection_refused() {
    // Point at a port that nothing listens on
    let client = ClientBuilder::new("Test/1.0 test@test.com")
        .rate_limit(100)
        .base_sec_url("http://127.0.0.1:1")
        .base_data_url("http://127.0.0.1:1")
        .base_efts_url("http://127.0.0.1:1")
        .build()
        .unwrap();

    let err = client.get_tickers().await.unwrap_err();
    assert!(matches!(err, Error::Request { .. }));
}

// ─── Multiple search hits ──────────────────────────────

#[tokio::test]
async fn search_multiple_hits() {
    let server = MockServer::start().await;
    let body = r#"{
        "hits": {
            "total": {"value": 250},
            "hits": [
                {
                    "_id": "doc1",
                    "_score": 20.0,
                    "_source": {
                        "ciks": ["320193"],
                        "display_names": ["Apple Inc."],
                        "form": "10-K",
                        "file_date": "2024-01-15",
                        "period_ending": "2023-12-31",
                        "adsh": "0000320193-24-000123",
                        "file_type": "10-K",
                        "file_description": "Annual Report"
                    }
                },
                {
                    "_id": "doc2",
                    "_score": 18.5,
                    "_source": {
                        "ciks": ["320193"],
                        "display_names": ["Apple Inc."],
                        "form": "10-Q",
                        "file_date": "2024-04-15",
                        "period_ending": "2024-03-31",
                        "adsh": "0000320193-24-000456",
                        "file_type": "10-Q",
                        "file_description": "Quarterly Report"
                    }
                }
            ]
        }
    }"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let result = client.search("Apple", None).await.unwrap();

    assert_eq!(result.total, 250);
    assert_eq!(result.hits.len(), 2);
    assert_eq!(result.hits[0].form, "10-K");
    assert_eq!(result.hits[1].form, "10-Q");
    assert!(result.hits[0].score > result.hits[1].score);
}

// ─── Empty responses ───────────────────────────────────

#[tokio::test]
async fn search_empty_results() {
    let server = MockServer::start().await;
    let body = r#"{"hits": {"total": {"value": 0}, "hits": []}}"#;

    Mock::given(method("GET"))
        .and(path("/LATEST/search-index"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let result = client
        .search("nonexistent query xyzzy", None)
        .await
        .unwrap();

    assert_eq!(result.total, 0);
    assert!(result.hits.is_empty());
}

#[tokio::test]
async fn get_tickers_empty_map() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/files/company_tickers.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string("{}"))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let tickers = client.get_tickers().await.unwrap();
    assert!(tickers.is_empty());
}

// ─── Submission with empty filing arrays ───────────────

#[tokio::test]
async fn get_submission_empty_filings() {
    let server = MockServer::start().await;
    let body = r#"{
        "cik": "1",
        "entityType": "",
        "sic": "",
        "sicDescription": "",
        "insiderTransactionForOwnerExists": 0,
        "insiderTransactionForIssuerExists": 0,
        "name": "Test Corp",
        "tickers": [],
        "exchanges": [],
        "ein": "",
        "description": "",
        "website": "",
        "investorWebsite": "",
        "category": "",
        "fiscalYearEnd": "",
        "stateOfIncorporation": "",
        "stateOfIncorporationDescription": "",
        "addresses": {
            "mailing": {
                "street1": null,
                "street2": null,
                "city": null,
                "stateOrCountry": null,
                "zipCode": null,
                "stateOrCountryDescription": null
            },
            "business": {
                "street1": null,
                "street2": null,
                "city": null,
                "stateOrCountry": null,
                "zipCode": null,
                "stateOrCountryDescription": null
            }
        },
        "phone": "",
        "flags": "",
        "formerNames": [],
        "filings": {
            "recent": {
                "accessionNumber": [],
                "filingDate": [],
                "reportDate": [],
                "acceptanceDateTime": [],
                "act": [],
                "form": [],
                "fileNumber": [],
                "filmNumber": [],
                "items": [],
                "size": [],
                "isXBRL": [],
                "isInlineXBRL": [],
                "primaryDocument": [],
                "primaryDocDescription": []
            },
            "files": []
        }
    }"#;

    Mock::given(method("GET"))
        .and(path("/submissions/CIK0000000001.json"))
        .respond_with(ResponseTemplate::new(200).set_body_string(body))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let sub = client.get_submission(Cik::new(1)).await.unwrap();

    assert_eq!(sub.name, "Test Corp");
    assert!(sub.tickers.is_empty());
    assert!(sub.filings.recent.accession_number.is_empty());
    assert!(sub.addresses.mailing.street1.is_none());
}
