"""Tests for Cygn Enterprise SDK."""

import json
from unittest.mock import patch, MagicMock
from urllib.error import HTTPError

import pytest

from cygn import CygnEnterprise, CygnAPIError


# ── Initialization ───────────────────────────────────────────────────────────


class TestInit:
    def test_license_key_auth(self):
        client = CygnEnterprise(license_key="cygn_ent_test")
        assert client._auth_header == "Bearer cygn_ent_test"

    def test_cygn_token_auth(self):
        client = CygnEnterprise(cygn_token="eyJhbGci")
        assert client._auth_header == "Cygn eyJhbGci"

    def test_cygn_token_takes_priority(self):
        client = CygnEnterprise(license_key="key", cygn_token="token")
        assert client._auth_header == "Cygn token"

    def test_no_auth_raises(self):
        with pytest.raises(ValueError, match="Either cygn_token or license_key is required"):
            CygnEnterprise()

    def test_custom_base_url_strips_trailing_slash(self):
        client = CygnEnterprise(license_key="k", base_url="https://custom.api/v2/")
        assert client._base_url == "https://custom.api/v2"

    def test_default_base_url(self):
        client = CygnEnterprise(license_key="k")
        assert client._base_url == "https://cygn.me/api/v1"

    def test_custom_timeout(self):
        client = CygnEnterprise(license_key="k", timeout=60)
        assert client._timeout == 60


# ── Helpers ──────────────────────────────────────────────────────────────────


def _mock_response(data: dict) -> MagicMock:
    """Create a mock urlopen response returning JSON data."""
    resp = MagicMock()
    resp.read.return_value = json.dumps(data).encode("utf-8")
    resp.__enter__ = lambda s: s
    resp.__exit__ = MagicMock(return_value=False)
    return resp


def _mock_http_error(status: int, data: dict) -> HTTPError:
    """Create a mock HTTPError with JSON body."""
    body = MagicMock()
    body.read.return_value = json.dumps(data).encode("utf-8")
    return HTTPError(
        url="https://cygn.me/api/v1/test",
        code=status,
        msg="Error",
        hdrs={},  # type: ignore[arg-type]
        fp=body,
    )


# ── Sign Image ───────────────────────────────────────────────────────────────


class TestSignImage:
    @patch("cygn.urlopen")
    def test_success(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({
            "success": True,
            "signedImage": "base64signed",
            "mimeType": "image/jpeg",
            "provenance": "captured",
            "signedAt": "2026-01-01T00:00:00Z",
        })

        client = CygnEnterprise(license_key="test")
        result = client.sign_image(image="base64img", app_name="TestApp")

        assert result.success is True
        assert result.signed_image == "base64signed"
        assert result.provenance == "captured"
        assert result.signed_at == "2026-01-01T00:00:00Z"

    @patch("cygn.urlopen")
    def test_correct_url(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({"success": True, "signedImage": "", "mimeType": "image/jpeg", "provenance": "captured", "signedAt": ""})

        client = CygnEnterprise(license_key="test")
        client.sign_image(image="base64img")

        call_args = mock_urlopen.call_args
        req = call_args[0][0]
        assert req.full_url == "https://cygn.me/api/v1/signing/c2pa"
        assert req.get_header("Authorization") == "Bearer test"
        assert req.get_header("Content-type") == "application/json"

    @patch("cygn.urlopen")
    def test_with_enterprise_info(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({
            "success": True,
            "signedImage": "data",
            "mimeType": "image/jpeg",
            "provenance": "captured",
            "signedAt": "2026-01-01T00:00:00Z",
            "enterprise": {
                "companyName": "Zomato",
                "label": "Captured by Zomato",
                "signedAt": "2026-01-01T00:00:00Z",
            },
        })

        client = CygnEnterprise(license_key="test")
        result = client.sign_image(image="img")

        assert result.enterprise is not None
        assert result.enterprise.company_name == "Zomato"
        assert result.enterprise.label == "Captured by Zomato"


# ── Detect Deepfake ──────────────────────────────────────────────────────────


class TestDetectDeepfake:
    @patch("cygn.urlopen")
    def test_success(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({
            "success": True,
            "mediaType": "image",
            "score": 0.15,
            "verdict": "likely_authentic",
            "signals": [{"name": "noise_analysis", "weight": 0.8, "description": "Consistent noise"}],
            "remaining": 498,
        })

        client = CygnEnterprise(license_key="test")
        result = client.detect_deepfake(content="base64img")

        assert result.success is True
        assert result.score == 0.15
        assert result.verdict == "likely_authentic"
        assert len(result.signals) == 1
        assert result.signals[0].name == "noise_analysis"
        assert result.remaining == 498


# ── Provenance Bundle ────────────────────────────────────────────────────────


class TestProvenanceBundle:
    @patch("cygn.urlopen")
    def test_success(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({
            "success": True,
            "signedImage": "signed_b64",
            "mimeType": "image/jpeg",
            "deepfake": {
                "score": 0.1,
                "verdict": "likely_authentic",
                "signals": [],
            },
            "signedAt": "2026-01-01T00:00:00Z",
        })

        client = CygnEnterprise(license_key="test")
        result = client.provenance_bundle(image="img_b64", app_name="TestApp")

        assert result.success is True
        assert result.signed_image == "signed_b64"
        assert result.deepfake.verdict == "likely_authentic"
        assert result.deepfake.score == 0.1


# ── Usage ────────────────────────────────────────────────────────────────────


class TestUsage:
    @patch("cygn.urlopen")
    def test_get_usage(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({
            "balance": 10000,
            "totalSpent": 5000,
            "currentMonth": {"signing": 3000},
        })

        client = CygnEnterprise(license_key="test")
        usage = client.get_usage()

        assert usage.balance == 10000
        assert usage.total_spent == 5000

    @patch("cygn.urlopen")
    def test_get_balance(self, mock_urlopen: MagicMock):
        mock_urlopen.return_value = _mock_response({
            "balance": 10000,
            "totalSpent": 5000,
            "currentMonth": {},
        })

        client = CygnEnterprise(license_key="test")
        balance = client.get_balance()
        assert balance == 10000


# ── Error Handling ───────────────────────────────────────────────────────────


class TestErrors:
    @patch("cygn.urlopen")
    def test_insufficient_balance(self, mock_urlopen: MagicMock):
        mock_urlopen.side_effect = _mock_http_error(402, {
            "error": "Insufficient balance",
            "balance": 50,
            "cost": 300,
        })

        client = CygnEnterprise(license_key="test")
        with pytest.raises(CygnAPIError) as exc_info:
            client.sign_image(image="img")

        assert exc_info.value.status == 402
        assert exc_info.value.code == "INSUFFICIENT_BALANCE"
        assert exc_info.value.balance == 50
        assert exc_info.value.cost == 300

    @patch("cygn.urlopen")
    def test_unauthorized(self, mock_urlopen: MagicMock):
        mock_urlopen.side_effect = _mock_http_error(401, {
            "error": "Authentication required",
        })

        client = CygnEnterprise(license_key="bad")
        with pytest.raises(CygnAPIError) as exc_info:
            client.detect_deepfake(content="img")

        assert exc_info.value.status == 401
        assert exc_info.value.code == "UNAUTHORIZED"

    @patch("cygn.urlopen")
    def test_rate_limited(self, mock_urlopen: MagicMock):
        mock_urlopen.side_effect = _mock_http_error(429, {
            "error": "Rate limited",
        })

        client = CygnEnterprise(license_key="test")
        with pytest.raises(CygnAPIError) as exc_info:
            client.sign_image(image="img")

        assert exc_info.value.status == 429
        assert exc_info.value.code == "RATE_LIMITED"

    def test_error_string(self):
        err = CygnAPIError(402, "Insufficient balance", balance=50, cost=300)
        assert str(err) == "[402] Insufficient balance"
        assert err.code == "INSUFFICIENT_BALANCE"
