"""Tests for xmldev.llm — LLM client with mocked responses."""
from __future__ import annotations

import json
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from xmldev.llm import LLMConfig, LLMError, LLMClient, PIIRedactor, build_prompt
from xmldev.schema import SchemaLoader


SCHEMA_STR = json.dumps({
    "root": {
        "name": "person",
        "attrs": {"id": {"name": "id", "type": "int", "required": True}},
        "children": [
            {"spec": {"name": "name", "text_type": "string"}, "min_occurs": 1},
        ],
    }
})


@pytest.fixture
def person_schema():
    return SchemaLoader.load(SCHEMA_STR)


# ---------------------------------------------------------------------------
# LLMConfig.load
# ---------------------------------------------------------------------------

def test_config_returns_none_if_no_file():
    cfg = LLMConfig.load("/non/existent/path.env")
    assert cfg is None


def test_config_returns_none_if_allow_llm_false(tmp_path):
    env_file = tmp_path / "xmldev.config.env"
    env_file.write_text("LLM_BASE_URL=https://api.openai.com/v1\nXMDEV_ALLOW_LLM=false\n")
    cfg = LLMConfig.load(str(env_file))
    assert cfg is None


def test_config_loads_when_allow_llm_true(tmp_path):
    env_file = tmp_path / "xmldev.config.env"
    env_file.write_text(
        "LLM_BASE_URL=https://api.openai.com/v1\n"
        "LLM_API_KEY=sk-test\n"
        "LLM_MODEL_ID=gpt-4o-mini\n"
        "XMDEV_ALLOW_LLM=true\n"
        "LLM_REDACT=true\n"
    )
    cfg = LLMConfig.load(str(env_file))
    assert cfg is not None
    assert cfg.allow_llm is True
    assert cfg.api_key == "sk-test"
    assert cfg.model_id == "gpt-4o-mini"
    assert cfg.redact is True


# ---------------------------------------------------------------------------
# PIIRedactor
# ---------------------------------------------------------------------------

def test_pii_redactor_tag_redaction():
    r = PIIRedactor(tags=["ssn"])
    xml = "<person><ssn>123-45-6789</ssn><name>Bob</name></person>"
    result = r.redact(xml)
    assert "[REDACTED]" in result
    assert "123-45-6789" not in result
    assert "Bob" in result


def test_pii_redactor_attr_redaction():
    r = PIIRedactor(attrs=["password"])
    xml = '<user password="s3cr3t" name="alice"/>'
    result = r.redact(xml)
    assert "[REDACTED]" in result
    assert "s3cr3t" not in result
    assert "alice" in result


# ---------------------------------------------------------------------------
# LLMClient (mocked)
# ---------------------------------------------------------------------------

def _make_mock_client(config: LLMConfig, response_text: str) -> LLMClient:
    """Create a LLMClient with mocked OpenAI call."""
    client = LLMClient(config)

    mock_response = MagicMock()
    mock_response.choices[0].message.content = response_text
    client._client.chat.completions.create = MagicMock(return_value=mock_response)
    return client


def test_llm_client_valid_response(person_schema, tmp_path):
    env_file = tmp_path / "cfg.env"
    env_file.write_text("LLM_BASE_URL=http://x\nLLM_API_KEY=k\nLLM_MODEL_ID=m\nXMDEV_ALLOW_LLM=true\nLLM_REDACT=false\n")
    cfg = LLMConfig.load(str(env_file))
    assert cfg is not None

    valid_xml = '<person id="1"><name>Bob</name></person>'
    client = _make_mock_client(cfg, valid_xml)

    result = client.repair(person_schema, '<person id="1"><nme>Bob</nme></person>')
    assert "<name>" in result


def test_llm_client_invalid_response_raises(person_schema, tmp_path):
    env_file = tmp_path / "cfg.env"
    env_file.write_text("LLM_BASE_URL=http://x\nLLM_API_KEY=k\nLLM_MODEL_ID=m\nXMDEV_ALLOW_LLM=true\nLLM_REDACT=false\n")
    cfg = LLMConfig.load(str(env_file))
    client = _make_mock_client(cfg, "this is not xml at all")
    with pytest.raises(LLMError, match="XM05"):
        client.repair(person_schema, "<broken/>")


def test_llm_client_xmldev_error_response(person_schema, tmp_path):
    env_file = tmp_path / "cfg.env"
    env_file.write_text("LLM_BASE_URL=http://x\nLLM_API_KEY=k\nLLM_MODEL_ID=m\nXMDEV_ALLOW_LLM=true\nLLM_REDACT=false\n")
    cfg = LLMConfig.load(str(env_file))
    client = _make_mock_client(cfg, "<xmldev_error>Cannot repair: missing data</xmldev_error>")
    with pytest.raises(LLMError, match="XM05"):
        client.repair(person_schema, "<broken/>")


def test_build_prompt_contains_root_name(person_schema):
    prompt = build_prompt(person_schema, "<broken/>")
    assert "person" in prompt
    assert "corrected XML" in prompt.lower() or "correct" in prompt.lower()
