"""TenderSense OS — Python SDK (Public API v1).

Μόνο stdlib — χωρίς dependencies. Χρήση:

    from tendersense_sdk import TenderSenseClient

    ts = TenderSenseClient(api_key="ts_...")
    print(ts.health())
    print(ts.external_events(since_hours=24))

    # Με session χρήστη (για /keys, /webhooks κ.λπ.):
    ts = TenderSenseClient(session_token="<bearer>")

Webhooks — επαλήθευση:

    TenderSenseClient.verify_webhook_signature(
        secret, timestamp, delivery_id, raw_body, signature)
"""
from __future__ import annotations

import hashlib
import hmac
import json
import time
import urllib.error
import urllib.request
from typing import Any


class TenderSenseError(Exception):
    pass


class TenderSenseClient:
    """Σύνδεση στο TenderSense OS Public API v1."""

    def __init__(
        self,
        base_url: str = "https://tendersense.gr",
        api_key: str | None = None,
        session_token: str | None = None,
        timeout: float = 30.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.api_key = api_key
        self.session_token = session_token
        self.timeout = timeout

    # ── internals ──────────────────────────────────────────────

    def _request(self, method: str, path: str,
                 payload: dict[str, Any] | None = None) -> dict[str, Any]:
        url = f"{self.base_url}{path}"
        headers = {"Accept": "application/json"}
        if self.api_key:
            headers["X-TenderSense-Key"] = self.api_key
        if self.session_token:
            headers["Authorization"] = f"Bearer {self.session_token}"
        data = None
        if payload is not None:
            data = json.dumps(payload).encode()
            headers["Content-Type"] = "application/json"
        req = urllib.request.Request(url, data=data, headers=headers, method=method)
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as resp:
                return json.loads(resp.read().decode() or "{}")
        except urllib.error.HTTPError as exc:
            raise TenderSenseError(
                f"{method} {path} -> {exc.code}: {exc.read().decode()[:300]}") from exc
        except urllib.error.URLError as exc:
            raise TenderSenseError(f"{method} {path} -> {exc.reason}") from exc

    # ── πυρήνας ─────────────────────────────────────────────────

    def health(self) -> dict[str, Any]:
        """Έκδοση + κατάσταση του API."""
        return self._request("GET", "/api/v1/health")

    def events(self, since_hours: int = 24, event_type: str | None = None,
               limit: int = 100) -> dict[str, Any]:
        """«Τι άλλαξε στον πυρήνα» (session)."""
        q = f"?since_hours={since_hours}&limit={limit}"
        if event_type:
            q += f"&event_type={event_type}"
        return self._request("GET", f"/api/v1/events{q}")

    def project_timeline(self, project_key: str) -> dict[str, Any]:
        """Χρονολόγιο έργου από replay (session)."""
        return self._request("GET", f"/api/v1/projects/{project_key}")

    # ── εξωτερικά (API key) ─────────────────────────────────────

    def external_events(self, since_hours: int = 24,
                        limit: int = 100) -> dict[str, Any]:
        """Ροή γεγονότων για εξωτερικά συστήματα (scope read:events)."""
        return self._request(
            "GET", f"/api/v1/external/events?since_hours={since_hours}&limit={limit}")

    def external_feedback(self, entity: str, reason: str,
                          evidence: list[str] | None = None) -> dict[str, Any]:
        """Feedback → typed action στον ιδιοκτήτη (scope write:feedback)."""
        return self._request("POST", "/api/v1/external/feedback", {
            "entity": entity,
            "reason": reason,
            "evidence": evidence or [],
        })

    # ── keys (session) ──────────────────────────────────────────

    def create_key(self, name: str, scopes: list[str]) -> dict[str, Any]:
        return self._request("POST", "/api/v1/keys",
                             {"name": name, "scopes": scopes})

    def list_keys(self) -> dict[str, Any]:
        return self._request("GET", "/api/v1/keys")

    def revoke_key(self, key_id: str) -> dict[str, Any]:
        return self._request("DELETE", f"/api/v1/keys/{key_id}")

    # ── webhooks (session) ──────────────────────────────────────

    def list_webhooks(self) -> dict[str, Any]:
        return self._request("GET", "/api/v1/webhooks")

    def register_webhook(self, url: str, events: list[str],
                         secret: str | None = None) -> dict[str, Any]:
        return self._request("POST", "/api/v1/webhooks", {
            "url": url, "events": events, "secret": secret,
        })

    def delete_webhook(self, webhook_id: str) -> dict[str, Any]:
        return self._request("DELETE", f"/api/v1/webhooks/{webhook_id}")

    # ── modules ─────────────────────────────────────────────────

    def list_modules(self) -> dict[str, Any]:
        return self._request("GET", "/api/v1/modules")

    def register_module(self, manifest: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/api/v1/modules", manifest)

    # ── webhook verification (replay-safe) ──────────────────────

    @staticmethod
    def verify_webhook_signature(
        secret: str,
        timestamp: str,
        delivery_id: str,
        raw_body: bytes,
        signature: str,
    ) -> bool:
        """Επαληθεύει X-TenderSense-Signature (HMAC-SHA256 πάνω σε
        timestamp + delivery-id + body)."""
        material = f"{timestamp}.{delivery_id}.".encode() + raw_body
        expected = hmac.new(secret.encode(), material, hashlib.sha256).hexdigest()
        return hmac.compare_digest(expected, signature)

    @staticmethod
    def verify_webhook_request(
        secret: str,
        headers: dict[str, str],
        raw_body: bytes,
        max_age_seconds: int = 300,
    ) -> bool:
        """Πλήρης έλεγχος εισερχόμενου webhook: υπογραφή + replay window.

        - signature: κάλυψη timestamp/delivery/body
        - max_age_seconds: απόρριψη καθυστερημένων (replay) deliveries
        Ο αποκλεισμός διπλών delivery-id (dedup) μένει στον καλούντα
        (αποθήκευση των id που έχουν ήδη επεξεργαστεί).
        """
        ts = headers.get("X-TenderSense-Timestamp", "")
        did = headers.get("X-TenderSense-Delivery-Id", "")
        sig = headers.get("X-TenderSense-Signature", "")
        if not ts or not did or not sig:
            return False
        if not ts.isdigit() or abs(int(time.time()) - int(ts)) > max_age_seconds:
            return False
        return TenderSenseClient.verify_webhook_signature(
            secret, ts, did, raw_body, sig)
