"""GBS (Global Broadband Statistics) tools for PT Research App.

MongoDB utils for Statistics and Sources. Used by PT Systems Agent for
human-supervised GBS data entry (see upc_query_agent issue #37).

Ref: pt-research-app schema (Statistics, Sources)
"""

from typing import Optional

from point_topic_mcp.core.utils import check_env_vars
from dotenv import load_dotenv

load_dotenv()

# GBS tools require PT Research App MongoDB connection
if check_env_vars("gbs_tools", ["PT_RESEARCH_DATABASE_URI"]):

    def _get_db():
        """Get MongoDB database client."""
        import os
        from pymongo import MongoClient

        uri = os.getenv("PT_RESEARCH_DATABASE_URI")
        client = MongoClient(uri)
        return client.get_default_database()

    # Prisma collection names = model names
    STATISTICS_COLLECTION = "Statistics"
    SOURCES_COLLECTION = "Sources"
    OPERATOR_COLLECTION = "Operator"
    GLOBAL_VARIABLES_COLLECTION = "GlobalVariables"

    # Valid enums from pt-research-app
    VALID_TYPES = ["broadband", "mobile", "iptv"]
    VALID_CHANNELS = ["Infrastructure", "Retail"]
    VALID_DOMAINS = ["Residential", "Business", "Total"]

    def get_gbs_status(
        country: str,
        operator: str,
        period: str,
        ctx=None,
    ) -> str:
        """
        Get GBS status for a country/operator/period: what exists, gaps, staleness.

        Args:
            country: Country name (e.g. "UK")
            operator: Operator name (exact match, case-sensitive)
            period: Quarter string (e.g. "2025Q1", "2024Q4")

        Returns:
            Summary of existing statistics, missing combinations (gaps), and
            staleness vs current stats period.
        """
        from bson import ObjectId

        db = _get_db()
        operators = db[OPERATOR_COLLECTION]
        statistics = db[STATISTICS_COLLECTION]
        global_vars = db[GLOBAL_VARIABLES_COLLECTION]

        # Resolve operator
        op = operators.find_one({"name": operator, "country": country})
        if not op:
            return f"Operator not found: '{operator}' in '{country}'. Check name and country match exactly."

        operator_id = op["_id"]
        technologies = op.get("technologies", [])

        # Get current stats period for staleness
        gv = global_vars.find_one({"isCurrent": True})
        current_period = gv.get("currentStatsPeriod", "unknown") if gv else "unknown"

        # Existing statistics for this operator + period
        existing = list(
            statistics.find(
                {"operatorId": operator_id, "period": period},
                {
                    "type": 1,
                    "tech": 1,
                    "channel": 1,
                    "domain": 1,
                    "state": 1,
                    "subscribers": 1,
                    "updatedAt": 1,
                },
            )
        )

        # Build set of existing combinations
        existing_combos = {
            (s["type"], s["tech"], s["channel"], s["domain"]) for s in existing
        }

        # Expected combinations: type × tech × channel × domain
        gaps = []
        for t in VALID_TYPES:
            for tech in technologies:
                for ch in VALID_CHANNELS:
                    for dom in VALID_DOMAINS:
                        combo = (t, tech, ch, dom)
                        if combo not in existing_combos:
                            gaps.append(f"  {t} / {tech} / {ch} / {dom}")

        # State counts
        state_names = {1: "pending", 2: "approved", 3: "published"}
        by_state = {}
        for s in existing:
            st = s.get("state", 1)
            by_state[st] = by_state.get(st, 0) + 1

        lines = [
            f"GBS status: {operator} ({country}) — {period}",
            f"operator_id: {str(operator_id)} (use for add_statistic, create_source)",
            "",
            f"Existing: {len(existing)} records",
            f"By state: {', '.join(f'{state_names.get(k, k)}: {v}' for k, v in sorted(by_state.items()))}",
            "",
            f"Current stats period: {current_period}",
            f"Staleness: period is {'current' if period == current_period else 'older than current'}",
            "",
            f"Gaps ({len(gaps)} missing combinations):",
            *gaps[:20],
        ]
        if len(gaps) > 20:
            lines.append(f"  ... and {len(gaps) - 20} more")

        return "\n".join(lines)

    def add_statistic(
        type: str,
        operator: str,
        operator_id: str,
        country: str,
        period: str,
        tech: str,
        channel: str,
        domain: str,
        subscribers: int,
        is_estimate: bool = False,
        restated: bool = False,
        notes: str = "",
        created_by: str = "gbs-agent",
        ctx=None,
    ) -> str:
        """
        Insert a Statistics (GBS) record with state: 1 (pending).

        Args:
            type: broadband | mobile | iptv
            operator: Operator display name
            operator_id: Operator ObjectId (from Operator collection)
            country: Country name
            period: Quarter string (e.g. "2025Q1")
            tech: Technology (must match operator's technologies)
            channel: Infrastructure | Retail
            domain: Residential | Business | Total
            subscribers: Subscriber count
            is_estimate: Whether value is estimated
            restated: Whether value was restated
            notes: Optional notes
            created_by: Creator identifier (e.g. user email)

        Returns:
            Inserted record id or error message.
        """
        from bson import ObjectId
        from datetime import datetime, timezone

        if type not in VALID_TYPES:
            return f"Invalid type: {type}. Must be one of {VALID_TYPES}"
        if channel not in VALID_CHANNELS:
            return f"Invalid channel: {channel}. Must be one of {VALID_CHANNELS}"
        if domain not in VALID_DOMAINS:
            return f"Invalid domain: {domain}. Must be one of {VALID_DOMAINS}"

        try:
            oid = ObjectId(operator_id)
        except Exception as e:
            return f"Invalid operator_id: {e}"

        db = _get_db()
        operators = db[OPERATOR_COLLECTION]
        op = operators.find_one({"_id": oid})
        if not op:
            return f"Operator not found: {operator_id}"
        techs = op.get("technologies", [])
        tech_match = next((t for t in techs if t.lower() == tech.lower()), None)
        if not tech_match:
            return f"Tech '{tech}' not valid for operator. Valid: {techs}"

        doc = {
            "type": type,
            "country": country,
            "operator": operator,
            "operatorId": oid,
            "period": period,
            "channel": channel,
            "domain": domain,
            "state": 1,  # pending
            "tech": tech_match,
            "subscribers": subscribers,
            "is_estimate": is_estimate,
            "restated": restated,
            "notes": notes or None,
            "createdBy": created_by,
            "updatedBy": created_by,
            "date": datetime.now(timezone.utc),
        }

        result = db[STATISTICS_COLLECTION].insert_one(doc)
        return f"Created statistic {str(result.inserted_id)} (state: pending)"

    def create_source(
        operator_id: str,
        year: int,
        quarter: int,
        type: str,
        url: str = "",
        file_url: Optional[str] = None,
        ctx=None,
    ) -> str:
        """
        Insert a Source record for an operator/period.

        Args:
            operator_id: Operator ObjectId (from Operator collection)
            year: Year (e.g. 2025)
            quarter: Quarter 1-4
            type: Source type (e.g. "report", "tariff")
            url: Source URL
            file_url: Optional file URL (e.g. S3 path)

        Returns:
            Inserted record id or error message.
        """
        from bson import ObjectId

        if quarter < 1 or quarter > 4:
            return f"Invalid quarter: {quarter}. Must be 1-4"

        try:
            oid = ObjectId(operator_id)
        except Exception as e:
            return f"Invalid operator_id: {e}"

        db = _get_db()
        operators = db[OPERATOR_COLLECTION]
        if not operators.find_one({"_id": oid}):
            return f"Operator not found: {operator_id}"

        doc = {
            "operatorId": oid,
            "year": year,
            "quarter": quarter,
            "type": type,
            "url": url or None,
            "fileUrl": file_url,
        }

        result = db[SOURCES_COLLECTION].insert_one(doc)
        return f"Created source {str(result.inserted_id)}"
