from __future__ import annotations

import os
import socket
import time
import uuid
from dataclasses import dataclass, field
from typing import Any, Dict, Iterable, List, Optional


@dataclass
class SpanContext:
    trace_id: bytes
    span_id: bytes
    parent_span_id: Optional[bytes]
    name: str
    kind: str
    start_time_unix_nano: int
    attributes: List[Dict[str, Any]] = field(default_factory=list)
    events: List[Dict[str, Any]] = field(default_factory=list)
    links: List[Dict[str, Any]] = field(default_factory=list)
    status: Optional[Dict[str, Any]] = None


def now_unix_nano() -> int:
    return time.time_ns()


def new_trace_id() -> bytes:
    return uuid.uuid4().bytes


def new_span_id() -> bytes:
    return uuid.uuid4().bytes[:8]


def any_value(value: Any) -> Dict[str, Any]:
    if isinstance(value, bool):
        return {"type": "bool", "bool_value": value}
    if isinstance(value, int) and not isinstance(value, bool):
        return {"type": "int", "int_value": value}
    if isinstance(value, float):
        return {"type": "double", "double_value": value}
    if isinstance(value, bytes):
        return {"type": "bytes", "bytes_value": value}
    if isinstance(value, str):
        return {"type": "string", "string_value": value}
    if isinstance(value, (list, tuple)):
        return {"type": "array", "array_value": [any_value(item) for item in value]}
    if isinstance(value, dict):
        items = []
        for key, val in value.items():
            items.append({"key": str(key), "value": any_value(val)})
        return {"type": "kvlist", "kvlist_value": items}
    return {"type": "string", "string_value": str(value)}


def native_from_any_value(value: Any) -> Any:
    if not isinstance(value, dict):
        return value
    value_type = value.get("type")
    if value_type == "bool":
        return value.get("bool_value")
    if value_type == "int":
        return value.get("int_value")
    if value_type == "double":
        return value.get("double_value")
    if value_type == "bytes":
        return value.get("bytes_value")
    if value_type == "string":
        return value.get("string_value")
    if value_type == "array":
        array_values = value.get("array_value") or []
        return [native_from_any_value(item) for item in array_values]
    if value_type == "kvlist":
        kv_values = value.get("kvlist_value") or []
        out: Dict[str, Any] = {}
        for entry in kv_values:
            key = entry.get("key")
            if key is None:
                continue
            out[str(key)] = native_from_any_value(entry.get("value", {}))
        return out
    return value.get("string_value")


def key_value(key: str, value: Any) -> Dict[str, Any]:
    return {"key": key, "value": any_value(value)}


def attributes_from_dict(attrs: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]:
    if not attrs:
        return []
    return [key_value(k, v) for k, v in attrs.items()]


def resource_attributes(base: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
    attrs = {
        "service.name": "highlighter-agent",
        "service.instance.id": str(uuid.uuid4()),
        "process.pid": os.getpid(),
        "host.name": socket.gethostname(),
    }
    if base:
        attrs.update(base)
    return attributes_from_dict(attrs)


def instrumentation_scope(name: str, version: Optional[str]) -> Dict[str, Any]:
    return {
        "name": name,
        "version": version,
        "attributes": [],
    }


def number_data_point(
    value: int | float,
    time_unix_nano: int,
    attributes: Optional[Dict[str, Any]] = None,
    start_time_unix_nano: Optional[int] = None,
) -> Dict[str, Any]:
    if isinstance(value, int) and not isinstance(value, bool):
        value_record = ("IntValue", {"value": value})
    else:
        value_record = ("DoubleValue", {"value": float(value)})
    return {
        "attributes": attributes_from_dict(attributes),
        "start_time_unix_nano": start_time_unix_nano,
        "time_unix_nano": time_unix_nano,
        "value": value_record,
    }


def gauge_metric(name: str, value: int | float, unit: Optional[str], time_unix_nano: int) -> Dict[str, Any]:
    return {
        "name": name,
        "description": None,
        "unit": unit,
        "data": ("Gauge", {"data_points": [number_data_point(value, time_unix_nano)]}),
    }


def sum_metric(
    name: str,
    value: int | float,
    unit: Optional[str],
    time_unix_nano: int,
    aggregation_temporality: str,
    is_monotonic: bool,
) -> Dict[str, Any]:
    return {
        "name": name,
        "description": None,
        "unit": unit,
        "data": (
            "Sum",
            {
                "data_points": [number_data_point(value, time_unix_nano)],
                "aggregation_temporality": aggregation_temporality,
                "is_monotonic": is_monotonic,
            },
        ),
    }


def histogram_data_point(
    value: int | float,
    time_unix_nano: int,
    attributes: Optional[Dict[str, Any]] = None,
    start_time_unix_nano: Optional[int] = None,
) -> Dict[str, Any]:
    value_float = float(value)
    return {
        "attributes": attributes_from_dict(attributes),
        "start_time_unix_nano": start_time_unix_nano,
        "time_unix_nano": time_unix_nano,
        "count": 1,
        "sum": value_float,
        "bucket_counts": [1],
        "explicit_bounds": [],
        "min": value_float,
        "max": value_float,
    }


def histogram_metric(
    name: str,
    value: int | float,
    unit: Optional[str],
    time_unix_nano: int,
    aggregation_temporality: str,
) -> Dict[str, Any]:
    return {
        "name": name,
        "description": None,
        "unit": unit,
        "data": (
            "Histogram",
            {
                "data_points": [histogram_data_point(value, time_unix_nano)],
                "aggregation_temporality": aggregation_temporality,
            },
        ),
    }
