"""REQ-110: Wiz adapter real API client + graceful degrade."""
import json
import os
import sys
from pathlib import Path
from unittest import mock

import pytest

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

from adapters.wiz.wiz_adapter import (
    WizClient, fetch_and_adapt, adapt, is_configured,
    _to_pcr, _emit_not_configured,
)


# A recorded Wiz GraphQL fixture (response shape).
WIZ_FIXTURE = {
    "data": {
        "issues": {
            "nodes": [
                {
                    "id": "issue-1",
                    "severity": "CRITICAL",
                    "title": "Public S3 bucket",
                    "status": "OPEN",
                    "entity": {"id": "arn:aws:s3:::x", "name": "x", "type": "S3_BUCKET", "cloudPlatform": "AWS"},
                    "control": {"id": "c1", "name": "no-public-buckets"},
                    "createdAt": "2026-07-20T00:00:00Z",
                },
                {
                    "id": "issue-2",
                    "severity": "HIGH",
                    "title": "Missing encryption",
                    "status": "OPEN",
                    "entity": {"id": "arn:aws:s3:::y", "name": "y", "type": "S3_BUCKET", "cloudPlatform": "AWS"},
                    "control": {"id": "c2", "name": "require-encryption"},
                    "createdAt": "2026-07-21T00:00:00Z",
                },
            ],
            "pageInfo": {"hasNextPage": False, "endCursor": None},
        }
    }
}


def test_wiz_client_requires_token_and_url(monkeypatch):
    monkeypatch.delenv("WIZ_API_TOKEN", raising=False)
    monkeypatch.delenv("WIZ_API_URL", raising=False)
    with pytest.raises(RuntimeError):
        WizClient()


def test_fetch_and_adapt_with_mock_client():
    """fetch_and_adapt translates Wiz issues to PolicyCheckResult via the real client."""
    client = mock.MagicMock(spec=WizClient)
    client.fetch_issues.return_value = WIZ_FIXTURE["data"]["issues"]["nodes"]
    pcrs = fetch_and_adapt("contract-1", client=client)
    assert len(pcrs) == 2
    assert pcrs[0]["engine"] == "wiz"
    assert pcrs[0]["ruleId"] == "no-public-buckets"
    assert pcrs[0]["severity"] == "critical"
    assert pcrs[0]["result"] == "fail"
    assert pcrs[1]["ruleId"] == "require-encryption"
    assert pcrs[1]["severity"] == "high"


def test_fetch_and_adapt_graceful_degrade_when_unconfigured(monkeypatch):
    monkeypatch.delenv("WIZ_API_TOKEN", raising=False)
    monkeypatch.delenv("WIZ_API_URL", raising=False)
    pcrs = fetch_and_adapt("contract-2")
    assert len(pcrs) == 1
    assert pcrs[0]["ruleId"] == "WIZ_NOT_CONFIGURED"
    assert pcrs[0]["result"] == "skipped"


def test_wiz_client_pagination(monkeypatch):
    """Pagination follows pageInfo.hasNextPage + endCursor."""
    monkeypatch.setenv("WIZ_API_TOKEN", "tok")
    monkeypatch.setenv("WIZ_API_URL", "https://api.wiz.io")
    client = WizClient()
    page1 = {
        "data": {"issues": {"nodes": [{"id": "i1", "severity": "HIGH", "title": "t1",
                                       "status": "OPEN", "entity": {}, "control": {}}],
                            "pageInfo": {"hasNextPage": True, "endCursor": "cursor1"}}}
    }
    page2 = {
        "data": {"issues": {"nodes": [{"id": "i2", "severity": "LOW", "title": "t2",
                                       "status": "OPEN", "entity": {}, "control": {}}],
                            "pageInfo": {"hasNextPage": False, "endCursor": None}}}
    }
    with mock.patch.object(client, "_post", side_effect=[page1, page2]):
        issues = client.fetch_issues()
    assert len(issues) == 2


def test_adapt_accepts_graphql_response_shape(tmp_path):
    """adapt() accepts a full GraphQL response shape ({data:{issues:{nodes:[...]}}})."""
    fixture = tmp_path / "wiz.json"
    fixture.write_text(json.dumps(WIZ_FIXTURE))
    pcrs = adapt(str(fixture), "contract-3")
    assert len(pcrs) == 2
    assert pcrs[0]["engine"] == "wiz"


def test_adapt_accepts_bare_list(tmp_path):
    fixture = tmp_path / "wiz.json"
    fixture.write_text(json.dumps(WIZ_FIXTURE["data"]["issues"]["nodes"]))
    pcrs = adapt(str(fixture), "contract-4")
    assert len(pcrs) == 2


def test_adapt_empty_issues_emits_not_configured(tmp_path):
    fixture = tmp_path / "wiz.json"
    fixture.write_text(json.dumps({"data": {"issues": {"nodes": []}}}))
    pcrs = adapt(str(fixture), "contract-5")
    assert len(pcrs) == 1
    assert pcrs[0]["ruleId"] == "WIZ_NOT_CONFIGURED"


def test_to_pcr_maps_severity_and_result():
    issue = {"id": "x", "severity": "INFORMATIONAL", "status": "RESOLVED",
             "title": "t", "entity": {"id": "r"}, "control": {"name": "rule"}}
    pcr = _to_pcr(issue, "c")
    assert pcr["severity"] == "info"
    assert pcr["result"] == "pass"
    assert pcr["ruleId"] == "rule"


def test_is_configured(monkeypatch):
    monkeypatch.setenv("WIZ_API_TOKEN", "tok")
    monkeypatch.setenv("WIZ_API_URL", "https://api.wiz.io")
    assert is_configured() is True
    monkeypatch.delenv("WIZ_API_URL", raising=False)
    assert is_configured() is False