"""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_list_operators():
    """list_operators returns operators sorted by name, with optional limit."""
    from bson import ObjectId

    import point_topic_mcp.tools.gbs_tools as gbs

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

    op_id_a = ObjectId()
    op_id_b = ObjectId()
    op_id_c = ObjectId()
    # Intentionally unsorted input to verify sorting is applied
    mock_ops = [
        {"_id": {"$oid": str(op_id_b)}, "name": "Zebra", "country": "UK", "technologies": ["FTTP"]},
        {"_id": {"$oid": str(op_id_a)}, "name": "Alpha", "country": "UK", "technologies": ["FTTP"]},
        {"_id": {"$oid": str(op_id_c)}, "name": "Beta", "country": "UK", "technologies": ["FTTP"]},
    ]
    
    with patch("point_topic_mcp.core.mongodb_utils.find_operators", return_value=mock_ops):
        # Test 1: basic call, should be sorted
        result = gbs.list_operators()
        assert "Operators:" in result
        assert "Alpha" in result
        assert "Beta" in result
        assert "Zebra" in result
        # Check ordering: Alpha should appear before Beta, Beta before Zebra
        alpha_pos = result.find("Alpha")
        beta_pos = result.find("Beta")
        zebra_pos = result.find("Zebra")
        assert alpha_pos < beta_pos < zebra_pos, "Operators should be sorted alphabetically"
        
        # Test 2: with limit
        result_limited = gbs.list_operators(limit=2)
        assert "showing 2 of 3" in result_limited
        assert "Alpha" in result_limited
        assert "Beta" in result_limited
        assert "Zebra" not in result_limited
        
        # Test 3: with country filter
        result_filtered = gbs.list_operators(country="UK")
        assert "(country filter: 'UK', 3 match(es))" in result_filtered


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("point_topic_mcp.core.mongodb_utils.find_one_operator", return_value=None):
        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 grouped by type, 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()
    mock_op = {"_id": {"$oid": str(op_id)}, "name": "BT", "country": "UK", "technologies": ["FTTP", "FTTC"]}
    mock_stats = [{"type": "broadband", "tech": "FTTP", "channel": "Infrastructure", "domain": "Residential", "state": 1}]
    mock_gv = {"isCurrent": True, "currentStatsPeriod": "2025Q1"}
    with (
        patch("point_topic_mcp.core.mongodb_utils.find_one_operator", return_value=mock_op),
        patch("point_topic_mcp.core.mongodb_utils.find_statistics", return_value=mock_stats),
        patch("point_topic_mcp.core.mongodb_utils.find_global_variables_current", return_value=mock_gv),
    ):
        result = gbs.get_gbs_status("UK", "BT", "2025Q1")
        assert "GBS status: BT (UK)" in result
        assert "operator_id: " + str(op_id) in result
        assert "technologies: FTTP, FTTC" in result
        assert "Existing: 1 records" in result
        assert "by type: broadband: 1" in result
        assert "by state: pending: 1" in result
        assert "Staleness: current" in result
        assert "broadband (" in result
        assert "mobile (" in result
        assert "iptv (" in result

    # Test staleness with older period
    mock_gv_ahead = {"isCurrent": True, "currentStatsPeriod": "2025Q3"}
    with (
        patch("point_topic_mcp.core.mongodb_utils.find_one_operator", return_value=mock_op),
        patch("point_topic_mcp.core.mongodb_utils.find_statistics", return_value=[]),
        patch("point_topic_mcp.core.mongodb_utils.find_global_variables_current", return_value=mock_gv_ahead),
    ):
        result2 = gbs.get_gbs_status("UK", "BT", "2025Q1")
        assert "2 quarters behind current (2025Q3)" in result2


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_id="000000000000000000000001",
        period="2025Q1",
        tech="FTTP",
        channel="Infrastructure",
        domain="Residential",
        subscribers=1000,
    )
    assert "Invalid type" in result

    # Invalid channel
    result = gbs.add_statistic(
        type="broadband",
        operator_id="000000000000000000000001",
        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_id="not-an-oid",
        period="2025Q1",
        tech="FTTP",
        channel="Infrastructure",
        domain="Residential",
        subscribers=1000,
    )
    assert "Invalid operator_id" in result

    # Invalid period format
    result = gbs.add_statistic(
        type="broadband",
        operator_id="000000000000000000000001",
        period="Q1 2025",
        tech="FTTP",
        channel="Infrastructure",
        domain="Residential",
        subscribers=1000,
    )
    assert "Invalid period" in result

    # Negative subscribers
    result = gbs.add_statistic(
        type="broadband",
        operator_id="000000000000000000000001",
        period="2025Q1",
        tech="FTTP",
        channel="Infrastructure",
        domain="Residential",
        subscribers=-100,
    )
    assert "Invalid subscribers" in result


def test_add_statistic_success():
    """add_statistic inserts, returns id, and auto-fills operator name/country from DB."""
    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()
    mock_op = {"_id": {"$oid": str(op_id)}, "name": "BT Group", "country": "United Kingdom", "technologies": ["FTTP", "FTTC"]}

    with (
        patch("point_topic_mcp.core.mongodb_utils.find_one_operator_by_id", return_value=mock_op),
        patch("point_topic_mcp.core.mongodb_utils.find_statistics", return_value=[]),
        patch("point_topic_mcp.core.mongodb_utils.insert_one_statistic", return_value=str(inserted_id)),
    ):
        result = gbs.add_statistic(
            type="broadband",
            operator_id=str(op_id),
            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
        assert "BT Group" in result
        assert "United Kingdom" in result

        from point_topic_mcp.core import mongodb_utils
        mongodb_utils.insert_one_statistic.assert_called_once()
        doc = mongodb_utils.insert_one_statistic.call_args[0][0]
        assert doc["subscribers"] == 50000
        assert doc["tech"] == "FTTP"
        assert doc["operator"] == "BT Group"
        assert doc["country"] == "United Kingdom"


def test_add_statistic_duplicate_rejected():
    """add_statistic rejects duplicate (same operator/period/type/tech/channel/domain)."""
    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()
    existing_id = ObjectId()
    mock_op = {"_id": {"$oid": str(op_id)}, "name": "BT", "country": "UK", "technologies": ["FTTP"]}
    mock_existing = [
        {"_id": {"$oid": str(existing_id)}, "type": "broadband", "tech": "FTTP",
         "channel": "Infrastructure", "domain": "Residential", "state": 1},
    ]

    with (
        patch("point_topic_mcp.core.mongodb_utils.find_one_operator_by_id", return_value=mock_op),
        patch("point_topic_mcp.core.mongodb_utils.find_statistics", return_value=mock_existing),
    ):
        result = gbs.add_statistic(
            type="broadband",
            operator_id=str(op_id),
            period="2025Q1",
            tech="FTTP",
            channel="Infrastructure",
            domain="Residential",
            subscribers=50000,
        )
        assert "Duplicate" in result
        assert str(existing_id) in result


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",
        url="https://example.com",
    )
    assert "Invalid operator_id" in result

    result = gbs.create_source(
        operator_id="000000000000000000000001",
        year=2025,
        quarter=5,
        type="report",
        url="https://example.com",
    )
    assert "Invalid quarter" in result

    result = gbs.create_source(
        operator_id="000000000000000000000001",
        year=2025,
        quarter=1,
        type="report",
    )
    assert "At least one of url or file_url" in result

    result = gbs.create_source(
        operator_id="000000000000000000000001",
        year=1800,
        quarter=1,
        type="report",
        url="https://example.com",
    )
    assert "Invalid year" in result

    result = gbs.create_source(
        operator_id="000000000000000000000001",
        year=2025,
        quarter=1,
        type="",
        url="https://example.com",
    )
    assert "Invalid type" in result


def test_create_source_success():
    """create_source inserts and returns created id with operator info."""
    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()
    mock_op = {"_id": {"$oid": str(op_id)}, "name": "BT Group"}

    with (
        patch("point_topic_mcp.core.mongodb_utils.find_one_operator_by_id", return_value=mock_op),
        patch("point_topic_mcp.core.mongodb_utils.find_sources", return_value=[]),
        patch("point_topic_mcp.core.mongodb_utils.insert_one_source", return_value=str(inserted_id)),
    ):
        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
        assert "BT Group" in result
        assert "2025Q1" in result

        from point_topic_mcp.core import mongodb_utils
        mongodb_utils.insert_one_source.assert_called_once()
        doc = mongodb_utils.insert_one_source.call_args[0][0]
        assert doc["year"] == 2025
        assert doc["quarter"] == 1
        assert doc["type"] == "report"


def test_create_source_duplicate_rejected():
    """create_source rejects duplicate (same operator/year/quarter/type/url)."""
    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()
    existing_id = ObjectId()
    mock_op = {"_id": {"$oid": str(op_id)}, "name": "BT"}
    mock_existing = [
        {"_id": {"$oid": str(existing_id)}, "type": "report",
         "url": "https://example.com/report", "fileUrl": None},
    ]

    with (
        patch("point_topic_mcp.core.mongodb_utils.find_one_operator_by_id", return_value=mock_op),
        patch("point_topic_mcp.core.mongodb_utils.find_sources", return_value=mock_existing),
    ):
        result = gbs.create_source(
            operator_id=str(op_id),
            year=2025,
            quarter=1,
            type="report",
            url="https://example.com/report",
        )
        assert "Duplicate" in result
        assert str(existing_id) in result
