from datetime import datetime, timezone

import httpx

from abuseipdb.responses import (
    BlacklistPlaintextResponse,
    BlacklistResponse,
    BulkReportResponse,
    CheckBlockResponse,
    CheckResponse,
    ClearAddressResponse,
    ReportResponse,
    ReportsPaginatedResponse,
)


def response(payload: object, *, headers: dict[str, str] | None = None) -> httpx.Response:
    return httpx.Response(
        200,
        json=payload,
        headers=headers,
        request=httpx.Request("GET", "https://example.test/check"),
    )


def test_check_response_maps_metadata_nested_reports_and_optionals() -> None:
    raw = response(
        {
            "data": {
                "ipAddress": "2001:db8::1",
                "isPublic": True,
                "ipVersion": 6,
                "isWhitelisted": None,
                "abuseConfidenceScore": 87,
                "countryCode": "US",
                "usageType": "Data Center/Web Hosting/Transit",
                "isp": "Example ISP",
                "domain": "example.net",
                "hostnames": ["host.example.net"],
                "isTor": False,
                "totalReports": 2,
                "numDistinctUsers": 1,
                "lastReportedAt": "2026-07-30T12:34:56+00:00",
                "countryName": "United States",
                "reports": [
                    {
                        "reportedAt": "2026-07-30T12:34:56+00:00",
                        "comment": "scan",
                        "categories": [14],
                        "reporterId": 42,
                        "reporterCountryCode": None,
                        "reporterCountryName": None,
                    }
                ],
            }
        },
        headers={
            "Content-Type": "application/json",
            "Cache-Control": "no-cache",
            "X-RateLimit-Limit": "1000",
            "X-RateLimit-Remaining": "999",
            "CF-Cache-Status": "DYNAMIC",
        },
    )
    parsed = CheckResponse.from_response(raw)
    assert parsed.ip_address == "2001:db8::1"
    assert parsed.ip_version == 6
    assert parsed.is_whitelisted is None
    assert parsed.abuse_confidence_score == 87
    assert parsed.last_reported_at == datetime(2026, 7, 30, 12, 34, 56, tzinfo=timezone.utc)
    assert parsed.reports[0].categories == (14,)
    assert parsed.reports[0].reporter_country_code is None
    assert parsed.x_ratelimit_limit == 1000
    assert parsed.raw_response is raw


def test_check_response_defaults_verbose_only_fields() -> None:
    raw = response(
        {
            "data": {
                "ipAddress": "8.8.8.8",
                "isPublic": True,
                "ipVersion": 4,
                "abuseConfidenceScore": 0,
                "countryCode": None,
                "usageType": None,
                "isp": None,
                "hostnames": [],
                "isTor": False,
                "totalReports": 0,
                "numDistinctUsers": 0,
                "lastReportedAt": None,
            }
        }
    )
    parsed = CheckResponse.from_response(raw)
    assert parsed.country_name is None
    assert parsed.domain is None
    assert parsed.reports == ()
    assert parsed.last_reported_at is None


def test_report_and_clear_responses() -> None:
    report = ReportResponse.from_response(
        response({"data": {"ipAddress": "203.0.113.1", "abuseConfidenceScore": 12}})
    )
    clear = ClearAddressResponse.from_response(response({"data": {"numReportsDeleted": 3}}))
    assert (report.ip_address, report.abuse_confidence_score) == ("203.0.113.1", 12)
    assert clear.num_reports_deleted == 3


def test_paginated_reports_maps_count_urls_and_nested_results() -> None:
    parsed = ReportsPaginatedResponse.from_response(
        response(
            {
                "data": {
                    "total": 1,
                    "page": 1,
                    "count": 1,
                    "perPage": 25,
                    "lastPage": 1,
                    "nextPageUrl": None,
                    "previousPageUrl": None,
                    "results": [
                        {
                            "reportedAt": "2026-01-02T03:04:05Z",
                            "comment": "ssh",
                            "categories": [18, 22],
                            "reporterId": 9,
                            "reporterCountryCode": "CA",
                            "reporterCountryName": "Canada",
                        }
                    ],
                }
            }
        )
    )
    assert (parsed.total, parsed.count, parsed.per_page) == (1, 1, 25)
    assert parsed.results[0].reported_at.tzinfo is timezone.utc
    assert parsed.next_page_url is None


def test_json_blacklist_maps_meta_and_nested_addresses() -> None:
    parsed = BlacklistResponse.from_response(
        response(
            {
                "meta": {"generatedAt": "2026-01-02T03:04:05+00:00"},
                "data": [
                    {
                        "ipAddress": "2001:db8::2",
                        "countryCode": None,
                        "abuseConfidenceScore": 100,
                        "lastReportedAt": "2026-01-01T00:00:00+00:00",
                    }
                ],
            }
        )
    )
    assert parsed.generated_at.year == 2026
    assert parsed.blacklisted_ips[0].country_code is None
    assert parsed.blacklisted_ips[0].ip_address == "2001:db8::2"


def test_plaintext_blacklist_ignores_blank_trailing_lines_and_reads_header_date() -> None:
    raw = httpx.Response(
        200,
        text="1.2.3.4\n2001:db8::3\n\n",
        headers={"X-Generated-At": "2026-01-02T03:04:05+00:00"},
        request=httpx.Request("GET", "https://example.test/blacklist"),
    )
    parsed = BlacklistPlaintextResponse.from_response(raw)
    assert parsed.addresses == ("1.2.3.4", "2001:db8::3")
    assert parsed.blacklisted_ips == parsed.addresses
    assert parsed.generated_at.year == 2026


def test_bulk_report_maps_invalid_rows() -> None:
    parsed = BulkReportResponse.from_response(
        response(
            {
                "data": {
                    "savedReports": 2,
                    "invalidReports": [{"error": "Invalid IP", "input": "bad", "rowNumber": 3}],
                }
            }
        )
    )
    assert parsed.saved_reports == 2
    assert parsed.invalid_reports[0].row_number == 3


def test_bulk_report_defaults_missing_invalid_rows() -> None:
    parsed = BulkReportResponse.from_response(response({"data": {"savedReports": 2}}))
    assert parsed.invalid_reports == ()


def test_check_block_maps_reported_addresses() -> None:
    parsed = CheckBlockResponse.from_response(
        response(
            {
                "data": {
                    "networkAddress": "2001:db8::",
                    "netmask": "ffff:ffff:ffff:ffff::",
                    "minAddress": "2001:db8::",
                    "maxAddress": "2001:db8::ffff:ffff:ffff:ffff",
                    "numPossibleHosts": 18446744073709551616,
                    "addressSpaceDesc": "Documentation",
                    "reportedAddress": [
                        {
                            "ipAddress": "2001:db8::1",
                            "numReports": 3,
                            "mostRecentReport": "2026-01-01T00:00:00+00:00",
                            "countryCode": None,
                            "abuseConfidenceScore": 25,
                        }
                    ],
                }
            }
        )
    )
    assert parsed.num_possible_hosts == 18446744073709551616
    assert parsed.reported_addresses[0].num_reports == 3
    assert parsed.reported_addresses[0].country_code is None
