"""Tests for pure functions in ietf_analyzer.analyzer (no API calls)."""

from __future__ import annotations

import json

import pytest

from ietf_analyzer.analyzer import Analyzer
from ietf_analyzer.models import Rating


# ---- _extract_json ----


class TestExtractJson:
    """Test the _extract_json static-ish method that strips markdown fences."""

    @staticmethod
    def _extract(text: str) -> str:
        # _extract_json is an instance method but only uses self for nothing,
        # so we call it on a dummy — avoid constructing full Analyzer (needs API key).
        return Analyzer._extract_json(None, text)

    def test_plain_json(self):
        raw = '{"key": "value"}'
        assert self._extract(raw) == '{"key": "value"}'

    def test_json_with_fences(self):
        raw = '```json\n{"key": "value"}\n```'
        assert self._extract(raw) == '{"key": "value"}'

    def test_json_with_plain_fences(self):
        raw = '```\n{"key": "value"}\n```'
        assert self._extract(raw) == '{"key": "value"}'

    def test_json_with_whitespace(self):
        raw = '  \n  {"key": "value"}  \n  '
        assert self._extract(raw) == '{"key": "value"}'

    def test_json_array_with_fences(self):
        raw = '```json\n[{"a": 1}, {"b": 2}]\n```'
        result = self._extract(raw)
        assert json.loads(result) == [{"a": 1}, {"b": 2}]

    def test_multiline_json_with_fences(self):
        raw = '```json\n{\n  "key": "value",\n  "num": 42\n}\n```'
        result = self._extract(raw)
        parsed = json.loads(result)
        assert parsed == {"key": "value", "num": 42}

    def test_no_fences_passthrough(self):
        raw = '[1, 2, 3]'
        assert self._extract(raw) == '[1, 2, 3]'

    def test_empty_string(self):
        assert self._extract('') == ''

    def test_fences_with_trailing_whitespace(self):
        raw = '```json\n{"ok": true}\n```  \n'
        result = self._extract(raw)
        assert json.loads(result) == {"ok": True}


# ---- _clamp_rating ----


class TestClampRating:
    def test_normal_values(self):
        assert Analyzer._clamp_rating(3) == 3
        assert Analyzer._clamp_rating(1) == 1
        assert Analyzer._clamp_rating(5) == 5

    def test_clamp_high(self):
        assert Analyzer._clamp_rating(10) == 5
        assert Analyzer._clamp_rating(99) == 5

    def test_clamp_low(self):
        assert Analyzer._clamp_rating(0) == 1
        assert Analyzer._clamp_rating(-5) == 1

    def test_float_truncated(self):
        assert Analyzer._clamp_rating(3.7) == 3
        assert Analyzer._clamp_rating(4.9) == 4

    def test_string_number(self):
        assert Analyzer._clamp_rating("4") == 4
        assert Analyzer._clamp_rating("1") == 1

    def test_invalid_returns_default(self):
        assert Analyzer._clamp_rating("abc") == 3
        assert Analyzer._clamp_rating(None) == 3
        assert Analyzer._clamp_rating([]) == 3

    def test_custom_default(self):
        assert Analyzer._clamp_rating("abc", default=2) == 2

    def test_custom_range(self):
        assert Analyzer._clamp_rating(8, lo=1, hi=10) == 8
        assert Analyzer._clamp_rating(15, lo=1, hi=10) == 10


# ---- _parse_rating ----


class TestParseRating:
    """Test _parse_rating with compact and verbose key formats."""

    @staticmethod
    def _parse(draft_name: str, data: dict) -> Rating:
        # _parse_rating calls self._clamp_rating, so we need a minimal object.
        # Create an object with just the _clamp_rating method bound.
        stub = object.__new__(Analyzer)
        return stub._parse_rating(draft_name, data)

    def test_compact_keys(self):
        data = {
            "s": "A summary",
            "n": 4, "nn": "novel approach",
            "m": 3, "mn": "early stage",
            "o": 2, "on": "minor overlap",
            "mo": 5, "mon": "strong momentum",
            "r": 4, "rn": "relevant",
            "c": ["A2A protocols"],
        }
        rating = self._parse("draft-test", data)
        assert rating.draft_name == "draft-test"
        assert rating.novelty == 4
        assert rating.maturity == 3
        assert rating.overlap == 2
        assert rating.momentum == 5
        assert rating.relevance == 4
        assert rating.summary == "A summary"
        assert rating.categories == ["A2A protocols"]

    def test_verbose_keys(self):
        data = {
            "summary": "A summary",
            "novelty": 3, "novelty_note": "ok",
            "maturity": 2, "maturity_note": "early",
            "overlap": 1, "overlap_note": "unique",
            "momentum": 4, "momentum_note": "active",
            "relevance": 5, "relevance_note": "core",
            "categories": ["AI safety/alignment"],
        }
        rating = self._parse("draft-test-2", data)
        assert rating.novelty == 3
        assert rating.relevance == 5
        assert rating.categories == ["AI safety/alignment"]

    def test_missing_keys_use_defaults(self):
        data = {}
        rating = self._parse("draft-empty", data)
        assert rating.novelty == 3  # default
        assert rating.maturity == 3
        assert rating.summary == ""
        assert rating.categories == []

    def test_out_of_range_clamped(self):
        data = {"n": 99, "m": -1, "o": 0, "mo": 10, "r": 6}
        rating = self._parse("draft-clamp", data)
        assert rating.novelty == 5
        assert rating.maturity == 1
        assert rating.overlap == 1
        assert rating.momentum == 5
        assert rating.relevance == 5
