"""Test GBS tools with mocked MongoDB (no real connection required)."""

import os
from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture(autouse=True)
def gbs_env(monkeypatch):
    """Ensure PT_RESEARCH_DATABASE_URI is set so gbs_tools module loads the functions."""
    monkeypatch.setenv("PT_RESEARCH_DATABASE_URI", "mongodb://localhost:27017/test")


@pytest.fixture
def mock_db():
    """Build a mock MongoDB db with Operator, Statistics, Sources, GlobalVariables."""
    from bson import ObjectId

    db = MagicMock()
    op_id = ObjectId()

    # Operator collection
    mock_operators = MagicMock()
    mock_operators.find_one.side_effect = lambda q: (
        {
            "_id": op_id,
            "name": "TestOperator",
            "country": "UK",
            "technologies": ["FTTP", "FTTC"],
        }
        if q.get("name") == "TestOperator" and q.get("country") == "UK"
        else (
            {"_id": op_id}
            if q == {"_id": op_id}
            else None
        )
    )
    db.__getitem__.side_effect = lambda name: {
        "Operator": mock_operators,
        "Statistics": MagicMock(),
        "Sources": MagicMock(),
        "GlobalVariables": MagicMock(),
    }.get(name, MagicMock())

    # For operator lookup by _id
    def operators_find_one(q):
        if "_id" in q and q["_id"] == op_id:
            return {"_id": op_id, "name": "TestOperator", "technologies": ["FTTP", "FTTC"]}
        return None

    mock_operators.find_one.side_effect = operators_find_one

    # Statistics: return some existing records
    mock_stats = MagicMock()
    mock_stats.find.return_value = iter([
        {"type": "broadband", "tech": "FTTP", "channel": "Infrastructure", "domain": "Residential", "state": 1},
    ])
    db.__getitem__.side_effect = lambda name: {
        "Operator": mock_operators,
        "Statistics": mock_stats,
        "Sources": MagicMock(),
        "GlobalVariables": MagicMock(),
    }.get(name, MagicMock())

    # GlobalVariables: current stats period
    mock_gv = MagicMock()
    mock_gv.find_one.return_value = {"isCurrent": True, "currentStatsPeriod": "2025Q1"}
    db.__getitem__.side_effect = lambda name: {
        "Operator": mock_operators,
        "Statistics": mock_stats,
        "Sources": MagicMock(),
        "GlobalVariables": mock_gv,
    }.get(name, MagicMock())

    # Sources insert
    mock_sources = MagicMock()
    mock_sources.insert_one.return_value.inserted_id = ObjectId()
    db.__getitem__.side_effect = lambda name: {
        "Operator": mock_operators,
        "Statistics": MagicMock(),
        "Sources": mock_sources,
        "GlobalVariables": mock_gv,
    }.get(name, MagicMock())

    return db


def _get_mock_db_collections(mock_db, op_id):
    """Return a consistent mock db with all collections properly set."""
    from bson import ObjectId

    mock_operators = MagicMock()
    mock_operators.find_one.side_effect = lambda q: (
        {"_id": op_id, "name": "TestOperator", "country": "UK", "technologies": ["FTTP", "FTTC"]}
        if (q.get("name") == "TestOperator" and q.get("country") == "UK") or q.get("_id") == op_id
        else None
    )

    mock_stats = MagicMock()
    mock_stats.find.return_value.__iter__ = lambda self: iter([
        {"type": "broadband", "tech": "FTTP", "channel": "Infrastructure", "domain": "Residential", "state": 1},
    ])

    mock_sources = MagicMock()
    inserted = ObjectId()
    mock_sources.insert_one.return_value.inserted_id = inserted

    mock_gv = MagicMock()
    mock_gv.find_one.return_value = {"isCurrent": True, "currentStatsPeriod": "2025Q1"}

    def getitem(name):
        colls = {
            "Operator": mock_operators,
            "Statistics": mock_stats,
            "Sources": mock_sources,
            "GlobalVariables": mock_gv,
        }
        return colls.get(name, MagicMock())

    mock_db.__getitem__ = getitem
    return mock_db


def test_get_gbs_status_operator_not_found():
    """get_gbs_status returns error when operator doesn't exist."""
    import point_topic_mcp.tools.gbs_tools as gbs

    if not hasattr(gbs, "get_gbs_status"):
        pytest.skip("GBS tools not loaded (PT_RESEARCH_DATABASE_URI not set)")

    with patch.object(gbs, "_get_db") as mock_get_db:
        mock_db = MagicMock()
        mock_operators = MagicMock()
        mock_operators.find_one.return_value = None
        mock_db.__getitem__ = lambda self, n: mock_operators if n == "Operator" else MagicMock()
        mock_get_db.return_value = mock_db

        result = gbs.get_gbs_status("UK", "NoSuchOperator", "2025Q1")
        assert "Operator not found" in result


def test_get_gbs_status_success():
    """get_gbs_status returns status with operator_id, gaps, staleness."""
    from bson import ObjectId

    import point_topic_mcp.tools.gbs_tools as gbs

    if not hasattr(gbs, "get_gbs_status"):
        pytest.skip("GBS tools not loaded (PT_RESEARCH_DATABASE_URI not set)")

    op_id = ObjectId()
    with patch.object(gbs, "_get_db") as mock_get_db:
        mock_operators = MagicMock()
        mock_operators.find_one.side_effect = lambda q: (
            {"_id": op_id, "name": "BT", "country": "UK", "technologies": ["FTTP", "FTTC"]}
            if (q.get("name") == "BT" and q.get("country") == "UK") or q.get("_id") == op_id
            else None
        )

        mock_stats = MagicMock()
        mock_stats.find.return_value = iter([
            {"type": "broadband", "tech": "FTTP", "channel": "Infrastructure", "domain": "Residential", "state": 1},
        ])

        mock_gv = MagicMock()
        mock_gv.find_one.return_value = {"isCurrent": True, "currentStatsPeriod": "2025Q1"}

        def getitem(self, n):
            return {"Operator": mock_operators, "Statistics": mock_stats, "GlobalVariables": mock_gv}.get(n, MagicMock())

        mock_db = MagicMock()
        mock_db.__getitem__ = getitem
        mock_get_db.return_value = mock_db

        result = gbs.get_gbs_status("UK", "BT", "2025Q1")

        assert "GBS status: BT (UK)" in result
        assert "operator_id:" in result
        assert str(op_id) in result
        assert "Existing:" in result
        assert "Gaps" in result
        assert "Staleness" in result


def test_add_statistic_validation():
    """add_statistic returns errors for invalid inputs."""
    import point_topic_mcp.tools.gbs_tools as gbs

    if not hasattr(gbs, "add_statistic"):
        pytest.skip("GBS tools not loaded")

    # Invalid type
    result = gbs.add_statistic(
        type="invalid",
        operator="BT",
        operator_id="000000000000000000000001",
        country="UK",
        period="2025Q1",
        tech="FTTP",
        channel="Infrastructure",
        domain="Residential",
        subscribers=1000,
    )
    assert "Invalid type" in result

    # Invalid channel
    result = gbs.add_statistic(
        type="broadband",
        operator="BT",
        operator_id="000000000000000000000001",
        country="UK",
        period="2025Q1",
        tech="FTTP",
        channel="Wrong",
        domain="Residential",
        subscribers=1000,
    )
    assert "Invalid channel" in result

    # Invalid operator_id format
    result = gbs.add_statistic(
        type="broadband",
        operator="BT",
        operator_id="not-an-oid",
        country="UK",
        period="2025Q1",
        tech="FTTP",
        channel="Infrastructure",
        domain="Residential",
        subscribers=1000,
    )
    assert "Invalid operator_id" in result


def test_add_statistic_success():
    """add_statistic inserts and returns created id."""
    from bson import ObjectId

    import point_topic_mcp.tools.gbs_tools as gbs

    if not hasattr(gbs, "add_statistic"):
        pytest.skip("GBS tools not loaded")

    op_id = ObjectId()
    inserted_id = ObjectId()

    with patch.object(gbs, "_get_db") as mock_get_db:
        mock_operators = MagicMock()
        mock_operators.find_one.return_value = {"_id": op_id, "technologies": ["FTTP", "FTTC"]}

        mock_stats = MagicMock()
        mock_stats.insert_one.return_value.inserted_id = inserted_id

        def getitem(self, n):
            return {"Operator": mock_operators, "Statistics": mock_stats}.get(n, MagicMock())

        mock_db = MagicMock()
        mock_db.__getitem__ = getitem
        mock_get_db.return_value = mock_db

        result = gbs.add_statistic(
            type="broadband",
            operator="BT",
            operator_id=str(op_id),
            country="UK",
            period="2025Q1",
            tech="FTTP",
            channel="Infrastructure",
            domain="Residential",
            subscribers=50000,
        )

        assert "Created statistic" in result
        assert str(inserted_id) in result
        assert "pending" in result
        mock_stats.insert_one.assert_called_once()
        doc = mock_stats.insert_one.call_args[0][0]
        assert doc["state"] == 1
        assert doc["subscribers"] == 50000


def test_create_source_validation():
    """create_source returns errors for invalid inputs."""
    import point_topic_mcp.tools.gbs_tools as gbs

    if not hasattr(gbs, "create_source"):
        pytest.skip("GBS tools not loaded")

    result = gbs.create_source(
        operator_id="bad-oid",
        year=2025,
        quarter=1,
        type="report",
    )
    assert "Invalid operator_id" in result

    result = gbs.create_source(
        operator_id="000000000000000000000001",
        year=2025,
        quarter=5,
        type="report",
    )
    assert "Invalid quarter" in result


def test_create_source_success():
    """create_source inserts and returns created id."""
    from bson import ObjectId

    import point_topic_mcp.tools.gbs_tools as gbs

    if not hasattr(gbs, "create_source"):
        pytest.skip("GBS tools not loaded")

    op_id = ObjectId()
    inserted_id = ObjectId()

    with patch.object(gbs, "_get_db") as mock_get_db:
        mock_operators = MagicMock()
        mock_operators.find_one.return_value = {"_id": op_id}

        mock_sources = MagicMock()
        mock_sources.insert_one.return_value.inserted_id = inserted_id

        def getitem(self, n):
            return {"Operator": mock_operators, "Sources": mock_sources}.get(n, MagicMock())

        mock_db = MagicMock()
        mock_db.__getitem__ = getitem
        mock_get_db.return_value = mock_db

        result = gbs.create_source(
            operator_id=str(op_id),
            year=2025,
            quarter=1,
            type="report",
            url="https://example.com/report",
        )

        assert "Created source" in result
        assert str(inserted_id) in result
        mock_sources.insert_one.assert_called_once()
        doc = mock_sources.insert_one.call_args[0][0]
        assert doc["year"] == 2025
        assert doc["quarter"] == 1
        assert doc["type"] == "report"
