from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))

import scripts.check_1c_metadata_kind_fixtures as fixtures  # noqa: E402


MANIFEST = ROOT / "config" / "1c_metadata_kind_fixtures.json"


def test_fixture_manifest_is_valid_and_forbids_direct_sql() -> None:
    manifest = fixtures.load_json(MANIFEST)

    assert fixtures.validate_manifest(manifest) == []
    assert manifest["policy"]["direct_sql_write_allowed"] is False
    assert manifest["designer"] == {
        "required_version": "8.5.1.1236",
        "access_mode": "operating_system_integrated_only",
        "credential_arguments_allowed": False,
        "export_script": "scripts/export_1c_extension_sources.ps1",
    }
    assert manifest["external_reference"]["usage"] == "external_reference_only_not_vendored"
    assert len(manifest["external_reference"]["commit"]) == 40
    assert {item["kind"] for item in manifest["fixtures"]} == {
        "CalculationRegister",
        "Sequence",
        "Interface",
    }


def test_offline_check_does_not_claim_live_readiness() -> None:
    result = fixtures.check_manifest(MANIFEST)

    assert result["passed"] is True
    assert result["status"] == "manifest_valid"
    assert result["counts"] == {"findings": 0, "fixtures": 3, "ready": 0, "gaps": 0}
    assert {item["status"] for item in result["fixtures"]} == {"not_checked"}


def test_live_check_distinguishes_ready_missing_and_unconfigured() -> None:
    calls: list[tuple[str, dict[str, Any]]] = []

    def fake_rpc(_url: str, method: str, payload: dict[str, Any], _timeout: float, _token: str) -> dict[str, Any]:
        calls.append((method, payload))
        if method == "extension.objects.find":
            return {
                "status": "ok",
                "objects": [
                    {
                        "kind": "CalculationRegister",
                        "name": "tt_РегистрРасчета1",
                        "guid": "a4dfe8f1-2ce8-43c9-8b41-3deb7238c5d5",
                        "activation_state": "saved_only",
                        "extension_guid": "fb26cf42-7609-11f1-828f-005056b0d483",
                    },
                    {
                        "kind": "Catalog",
                        "name": "tt_Последовательность1",
                        "guid": "11111111-1111-1111-1111-111111111111",
                        "activation_state": "saved_only",
                    },
                ],
            }
        return {"status": "ok"}

    result = fixtures.check_manifest(MANIFEST, live=True, rpc_call=fake_rpc)

    by_kind = {item["kind"]: item for item in result["fixtures"]}
    assert by_kind["CalculationRegister"]["status"] == "fixture_ready"
    assert by_kind["Sequence"]["status"] == "fixture_missing"
    assert by_kind["Interface"]["status"] == "target_not_configured"
    assert [item["method"] for item in by_kind["CalculationRegister"]["smoke"]] == [
        "metadata.object.get",
        "metadata.object.properties",
        "metadata.object.attributes",
        "metadata.object.related",
    ]
    extension_find_calls = [payload for method, payload in calls if method == "extension.objects.find"]
    assert len(extension_find_calls) == 1
    calc_find_payload = extension_find_calls[0]
    assert "kind" not in calc_find_payload
    assert calc_find_payload["use_cache"] is True
    assert calc_find_payload["full_scan"] is False
    assert calc_find_payload["refresh_cache"] is False
    assert result["passed"] is True
    assert result["status"] == "gaps_found"


def test_require_ready_fails_when_a_fixture_is_missing() -> None:
    def fake_rpc(_url: str, method: str, _payload: dict[str, Any], _timeout: float, _token: str) -> dict[str, Any]:
        if method == "metadata.objects.list":
            return {"status": "not_found", "objects": []}
        return {"status": "not_found", "objects": []}

    result = fixtures.check_manifest(
        MANIFEST,
        live=True,
        require_ready=True,
        base_overrides={"interface_legacy_base": "legacy_test"},
        rpc_call=fake_rpc,
    )

    assert result["passed"] is False
    assert result["counts"]["ready"] == 0
    assert result["counts"]["gaps"] == 3


def test_live_check_reuses_extension_scan_transport_failure() -> None:
    calls = 0

    def fake_rpc(_url: str, _method: str, _payload: dict[str, Any], _timeout: float, _token: str) -> dict[str, Any]:
        nonlocal calls
        calls += 1
        raise TimeoutError("scan timed out")

    result = fixtures.check_manifest(MANIFEST, live=True, rpc_call=fake_rpc)

    by_kind = {item["kind"]: item for item in result["fixtures"]}
    assert calls == 1
    assert by_kind["CalculationRegister"]["error"] == "TimeoutError"
    assert "probe_error_reused" not in by_kind["CalculationRegister"]
    assert by_kind["Sequence"]["error"] == "TimeoutError"
    assert by_kind["Sequence"]["probe_error_reused"] is True


def test_manifest_rejects_credential_like_keys(tmp_path: Path) -> None:
    value = fixtures.load_json(MANIFEST)
    value["fixtures"][0]["password"] = "must-not-be-here"
    path = tmp_path / "manifest.json"
    path.write_text(json.dumps(value, ensure_ascii=False), encoding="utf-8")

    result = fixtures.check_manifest(path)

    assert result["passed"] is False
    assert any(item["code"] == "credential_key_in_manifest" for item in result["findings"])


def test_manifest_requires_safe_designer_access_and_pinned_reference() -> None:
    value = fixtures.load_json(MANIFEST)
    value["designer"]["access_mode"] = "interactive_credentials"
    value["external_reference"]["commit"] = "main"

    findings = fixtures.validate_manifest(value)

    assert {item["code"] for item in findings} >= {
        "unsafe_designer_access_mode",
        "invalid_external_reference_commit",
    }


def test_designer_export_script_has_no_user_or_credential_parameters() -> None:
    script = (ROOT / "scripts" / "export_1c_extension_sources.ps1").read_text(encoding="utf-8")

    assert "/WA+" in script
    assert "/N" not in script
    assert "/P" not in script
    assert "UserName" not in script
    assert "Credential" not in script
    assert "/DumpConfigToFiles" in script
    assert "-Extension" in script
