from __future__ import annotations

import importlib
import json
from dataclasses import dataclass
from typing import Any, Callable
from urllib import request
from urllib.error import URLError

from one_run.errors import OneRunError
from one_run.models import ListenerConfig
from one_run.commands.events_common import normalize_values


def load_handler(handler: str | None) -> Callable[[dict[str, Any]], None] | None:
    if not handler:
        return None
    if ":" not in handler:
        raise OneRunError("handler must be in format module:function")
    module_name, func_name = handler.split(":", 1)
    try:
        mod = importlib.import_module(module_name)
    except Exception as exc:
        raise OneRunError(f"failed to import handler module: {module_name}") from exc
    fn = getattr(mod, func_name, None)
    if not callable(fn):
        raise OneRunError(f"handler is not callable: {handler}")
    return fn


@dataclass
class ListenerRoute:
    label: str
    kind: str
    action: str | None
    webhook_url: str | None
    allowed_events: set[str]
    allowed_states: set[str]
    required_tags: set[str]
    callback: Callable[[dict[str, Any]], None] | None


def _post_json(url: str, payload: dict[str, Any], *, timeout_sec: float) -> None:
    data = json.dumps(payload).encode("utf-8")
    req = request.Request(url, data=data, method="POST", headers={"content-type": "application/json"})
    with request.urlopen(req, timeout=timeout_sec) as resp:
        code = getattr(resp, "status", 200)
        if int(code) >= 400:
            raise OneRunError(f"webhook returned status {code}")


def _make_webhook_callback(
    *,
    webhook_url: str,
    timeout_sec: float,
    action: str | None,
) -> Callable[[dict[str, Any]], None]:
    def _callback(event_payload: dict[str, Any]) -> None:
        out = {
            "kind": "webhook",
            "action": action or "event",
            "event": event_payload,
        }
        try:
            _post_json(webhook_url, out, timeout_sec=timeout_sec)
        except (URLError, TimeoutError, OneRunError):
            return

    return _callback


def route_from_listener(listener: ListenerConfig, *, idx: int) -> ListenerRoute:
    label = listener.name or f"{listener.kind}-{idx + 1}"
    callback: Callable[[dict[str, Any]], None] | None
    if listener.kind == "local":
        callback = load_handler(listener.handler) if listener.handler else None
    else:
        callback = _make_webhook_callback(
            webhook_url=listener.webhook_url or "",
            timeout_sec=listener.timeout_sec or 3.0,
            action=listener.action,
        )
    return ListenerRoute(
        label=label,
        kind=listener.kind,
        action=listener.action,
        webhook_url=listener.webhook_url,
        allowed_events=normalize_values(listener.events),
        allowed_states=normalize_values(listener.states),
        required_tags=normalize_values(listener.tags),
        callback=callback,
    )
