from __future__ import annotations

import json
import time
from collections.abc import Iterable

from .graylog_mcp import GraylogMcpClient


def _records(value: object) -> Iterable[dict[str, object]]:
    if isinstance(value, dict):
        schema = value.get("schema")
        datarows = value.get("datarows")
        if isinstance(schema, list) and isinstance(datarows, list):
            fields = [str(column.get("field", column.get("name", ""))) for column in schema if isinstance(column, dict)]
            for row in datarows:
                if isinstance(row, list):
                    yield {field: row[index] for index, field in enumerate(fields) if field and index < len(row)}
            return
        for key in ("rows", "data", "results", "messages"):
            if isinstance(value.get(key), list):
                yield from (item for item in value[key] if isinstance(item, dict))
                return
        if value:
            yield value


def _number(value: object) -> int:
    try:
        return int(float(str(value)))
    except (TypeError, ValueError):
        return 0


def _count_from_records(records: list[dict[str, object]]) -> int:
    if not records:
        return 0
    keys = ("count", "event_count", "events", "total", "COUNT()", "count()")
    for record in records:
        for key, value in record.items():
            normalized_key = str(key).lower()
            if normalized_key in {item.lower() for item in keys} or "count" in normalized_key:
                count = _number(value)
                if count:
                    return count
    if len(records) == 1:
        numeric_values = [_number(value) for value in records[0].values()]
        return max(numeric_values or [0])
    return len(records)


def _schema_properties(schema: object) -> set[str]:
    if not isinstance(schema, dict):
        return set()
    properties = schema.get("properties")
    if not isinstance(properties, dict):
        return set()
    return {str(key) for key in properties}


def _filter_supported(arguments: dict[str, object], properties: set[str]) -> dict[str, object]:
    if not properties:
        return arguments
    return {key: value for key, value in arguments.items() if key in properties}


class GraylogAggregateSource:
    def __init__(self, client: GraylogMcpClient, stream: str, query: str = "*") -> None:
        self.client = client
        self.stream = stream
        self.query = query or "*"

    def fetch_count(self, *, range_seconds: int = 300, probe_status: dict[str, object] | None = None, deadline_monotonic: float | None = None) -> dict[str, object]:
        if probe_status is not None:
            status = dict(probe_status)
        else:
            try:
                status = self.client.probe()
            except RuntimeError as exc:
                return {
                    "status": "error",
                    "source": "graylog_mcp_aggregate",
                    "aggregate_status": "error",
                    "aggregate_events": 0,
                    "aggregate_records": 0,
                    "aggregate_error": f"probe_error: {exc}",
                    "aggregate_errors": [f"probe_error: {exc}"],
                    "aggregate_schema_properties": [],
                }
        tool_schemas = status.get("tool_schemas", {}) if isinstance(status.get("tool_schemas"), dict) else {}
        aggregate_schema = tool_schemas.get("aggregate_messages", {}) if isinstance(tool_schemas, dict) else {}
        properties = _schema_properties(aggregate_schema)
        base = {
            "query": self.query,
            "streams": [self.stream] if self.stream else [],
            "range_seconds": max(1, int(range_seconds)),
        }
        schema_variants: list[dict[str, object]] = []
        metric_keys = [key for key in ("metrics", "series") if key in properties]
        group_keys = [key for key in ("groupings", "fields") if key in properties]
        if properties and metric_keys:
            for metric_key in metric_keys:
                for metric_value in (["count()"], ["count"], [{"function": "count"}]):
                    candidate = {**base, metric_key: metric_value}
                    for group_key in group_keys:
                        candidate[group_key] = []
                    schema_variants.append(_filter_supported(candidate, properties))
        if properties:
            schema_variants.append(_filter_supported(base, properties))
        fallback_variants: list[dict[str, object]] = [
            {
                "query": self.query,
                "streams": [self.stream] if self.stream else [],
                "range_seconds": max(1, int(range_seconds)),
                "groupings": [],
                "metrics": ["count()"],
            },
            {
                "query": self.query,
                "streams": [self.stream] if self.stream else [],
                "range_seconds": max(1, int(range_seconds)),
                "groupings": [],
                "metrics": ["count"],
            },
            {
                "query": self.query,
                "streams": [self.stream] if self.stream else [],
                "range_seconds": max(1, int(range_seconds)),
                "groupings": [],
                "metrics": [{"function": "count"}],
            },
        ]
        variants = [*schema_variants, *fallback_variants]
        seen_variants: set[str] = set()
        errors: list[str] = []
        for arguments in variants:
            if deadline_monotonic is not None and time.monotonic() >= deadline_monotonic:
                errors.append("poll_budget_exceeded")
                break
            variant_key = json.dumps(arguments, sort_keys=True)
            if variant_key in seen_variants:
                continue
            seen_variants.add(variant_key)
            try:
                result = self.client.call_tool("aggregate_messages", arguments)
            except RuntimeError as exc:
                errors.append(f"{arguments}: {exc}")
                continue
            content = result.get("result", {}).get("content", []) if isinstance(result.get("result"), dict) else []
            if isinstance(result.get("result"), dict) and result["result"].get("isError"):
                detail = next((str(item.get("text")) for item in content if isinstance(item, dict) and item.get("type") == "text"), "Graylog aggregate failed")
                errors.append(f"{arguments}: {detail}")
                continue
            records: list[dict[str, object]] = []
            for item in content if isinstance(content, list) else []:
                if isinstance(item, dict) and item.get("type") == "text":
                    try:
                        records.extend(_records(json.loads(str(item.get("text", "")))))
                    except json.JSONDecodeError:
                        continue
            return {
                **status,
                "source": "graylog_mcp_aggregate",
                "aggregate_status": "ok",
                "aggregate_events": _count_from_records(records),
                "aggregate_records": len(records),
                "aggregate_arguments": arguments,
                "aggregate_schema_properties": sorted(properties),
            }
        return {
            **status,
            "source": "graylog_mcp_aggregate",
            "aggregate_status": "error",
            "aggregate_events": 0,
            "aggregate_records": 0,
            "aggregate_error": "; ".join(errors[-3:]) or "aggregate_messages failed",
            "aggregate_errors": errors[-6:],
            "aggregate_schema_properties": sorted(properties),
        }
