from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen


@dataclass
class OmniDatastreamError(Exception):
    status: int
    payload: dict[str, Any]

    def __str__(self) -> str:
        message = self.payload.get("message") if isinstance(self.payload, dict) else None
        code = self.payload.get("code") if isinstance(self.payload, dict) else None
        return f"OmniDatastreamError(status={self.status}, code={code}, message={message})"


class OmniDatastreamClient:
    def __init__(
        self,
        api_key: str | None = None,
        bearer_token: str | None = None,
        base_url: str = "https://api.secapi.ai",
        api_version: str = "2026-03-19",
    ) -> None:
        self.api_key = api_key
        self.bearer_token = bearer_token
        self.base_url = base_url.rstrip("/")
        self.api_version = api_version

    def _headers(self) -> dict[str, str]:
        headers = {
            "accept": "application/json",
            "content-type": "application/json",
            "omni-version": self.api_version,
            "user-agent": f"omni-datastream-py/{self.api_version}",
        }
        if self.bearer_token:
            headers["authorization"] = f"Bearer {self.bearer_token}"
        if self.api_key:
            headers["x-api-key"] = self.api_key
        return headers

    def _request(self, method: str, path: str, params: dict[str, Any] | None = None, body: dict[str, Any] | None = None) -> dict[str, Any]:
        filtered_params = {
            key: value
            for key, value in (params or {}).items()
            if value is not None and value != ""
        }
        query = f"?{urlencode(filtered_params, doseq=True)}" if filtered_params else ""
        payload = json.dumps(body).encode("utf-8") if body is not None else None
        request = Request(f"{self.base_url}{path}{query}", data=payload, method=method, headers=self._headers())

        try:
            with urlopen(request) as response:
                if response.status == 204:
                    return {}
                raw = response.read().decode("utf-8")
                if not raw.strip():
                    return {}
                return json.loads(raw)
        except HTTPError as error:
            raw_payload = error.read().decode("utf-8", errors="replace")
            try:
                payload = json.loads(raw_payload)
            except json.JSONDecodeError:
                payload = {"message": raw_payload or error.reason}
            raise OmniDatastreamError(status=error.code, payload=payload) from error

    def health(self) -> dict[str, Any]:
        return self._request("GET", "/healthz")

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

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

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

    def dashboard_overview(self) -> dict[str, Any]:
        return self._request("GET", "/v1/dashboard/overview")

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

    def create_api_key(self, *, label: str | None = None, scopes: list[str] | None = None, livemode: bool | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/api_keys", body={"label": label, "scopes": scopes, "livemode": livemode})

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

    def create_agent_bootstrap_token(
        self,
        *,
        label: str | None = None,
        scopes: list[str] | None = None,
        ttl_seconds: int | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "POST",
            "/v1/agent/bootstrap_tokens",
            body={"label": label, "scopes": scopes, "ttlSeconds": ttl_seconds},
        )

    def bootstrap_agent(self, *, token: str, label: str | None = None, scopes: list[str] | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/agent/bootstrap", body={"token": token, "label": label, "scopes": scopes})

    def quote_billing(
        self,
        *,
        plan_key: str | None = None,
        meter_class: str | None = None,
        path: str | None = None,
        method: str | None = None,
        units: int | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "POST",
            "/v1/billing/quote",
            body={"planKey": plan_key, "meterClass": meter_class, "path": path, "method": method, "units": units},
        )

    def update_billing_budget(
        self,
        *,
        spend_cap_cents: int | None = None,
        soft_cap_cents: int | None = None,
        approval_threshold_cents: int | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "PUT",
            "/v1/billing/budget",
            body={
                "spendCapCents": spend_cap_cents,
                "softCapCents": soft_cap_cents,
                "approvalThresholdCents": approval_threshold_cents,
            },
        )

    def create_checkout_session(self, *, plan_key: str, success_url: str | None = None, cancel_url: str | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/billing/checkout", body={"planKey": plan_key, "successUrl": success_url, "cancelUrl": cancel_url})

    def create_billing_portal_session(self, *, return_url: str | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/billing/portal", body={"returnUrl": return_url})

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

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

    def events(
        self,
        *,
        kind: str | None = None,
        type: str | None = None,
        request_id: str | None = None,
        since: str | None = None,
        limit: int | None = None,
    ) -> dict[str, Any]:
        return self._request("GET", "/v1/events", params={"kind": kind, "type": type, "requestId": request_id, "since": since, "limit": limit})

    def export_events(
        self,
        *,
        kind: str | None = None,
        type: str | None = None,
        request_id: str | None = None,
        since: str | None = None,
        limit: int | None = None,
        format: str = "json",
    ) -> dict[str, Any]:
        return self._request(
            "GET",
            "/v1/events/export",
            params={"kind": kind, "type": type, "requestId": request_id, "since": since, "limit": limit, "format": format},
        )

    def request_diagnostics(self, request_id: str) -> dict[str, Any]:
        return self._request("GET", f"/v1/diagnostics/requests/{request_id}")

    def list_admin_organizations(self, *, q: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/admin/orgs", params={"q": q, "limit": limit})

    def get_admin_organization(self, org_id: str, *, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", f"/v1/admin/orgs/{org_id}", params={"limit": limit})

    def get_admin_request_diagnostics(self, org_id: str, request_id: str) -> dict[str, Any]:
        return self._request("GET", f"/v1/admin/orgs/{org_id}/requests/{request_id}")

    def get_admin_delivery_summary(
        self,
        org_id: str,
        *,
        since: str | None = None,
        limit: int | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "GET",
            f"/v1/admin/orgs/{org_id}/deliveries/summary",
            params={"since": since, "limit": limit},
        )

    def delivery_summary(self, *, since: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/diagnostics/deliveries/summary", params={"since": since, "limit": limit})

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

    def export_observability(self, *, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/observability/export", params={"limit": limit})

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

    def create_webhook_endpoint(
        self,
        *,
        destination_url: str,
        description: str | None = None,
        subscribed_event_types: list[str] | None = None,
        livemode: bool | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "POST",
            "/v1/webhook_endpoints",
            body={
                "destinationUrl": destination_url,
                "description": description,
                "subscribedEventTypes": subscribed_event_types,
                "livemode": livemode,
            },
        )

    def rotate_webhook_endpoint_secret(self, webhook_id: str) -> dict[str, Any]:
        return self._request("POST", f"/v1/webhook_endpoints/{webhook_id}/rotate_secret")

    def list_webhook_deliveries(self, webhook_id: str, *, event_id: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", f"/v1/webhook_endpoints/{webhook_id}/deliveries", params={"eventId": event_id, "limit": limit})

    def replay_webhook_delivery(self, webhook_id: str, delivery_id: str) -> dict[str, Any]:
        return self._request("POST", f"/v1/webhook_endpoints/{webhook_id}/deliveries/{delivery_id}/replay")

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

    def create_stream_subscription(
        self,
        *,
        description: str | None = None,
        event_types: list[str] | None = None,
        transport: str | None = None,
        livemode: bool | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "POST",
            "/v1/stream_subscriptions",
            body={
                "description": description,
                "eventTypes": event_types,
                "transport": transport,
                "livemode": livemode,
            },
        )

    def stream_events(self, stream_id: str, *, cursor: str | None = None, type: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", f"/v1/stream_subscriptions/{stream_id}/events", params={"cursor": cursor, "type": type, "limit": limit})

    def resolve_entity(self, *, ticker: str | None = None, cik: str | None = None, name: str | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/entities/resolve", params={"ticker": ticker, "cik": cik, "name": name})

    def search_entities(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/entities", params=params)

    def search_filings(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/filings", params=params)

    def filing_by_accession(self, accession_number: str, **params: Any) -> dict[str, Any]:
        return self._request("GET", f"/v1/filings/{accession_number}", params=params)

    def latest_filing(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/filings/latest", params=params)

    def render_latest_filing(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/filings/latest/render", params=params)

    def latest_section(self, section_key: str, **params: Any) -> dict[str, Any]:
        return self._request("GET", f"/v1/filings/latest/sections/{section_key}", params=params)

    def filing_section_by_accession(self, accession_number: str, section_key: str, **params: Any) -> dict[str, Any]:
        return self._request("GET", f"/v1/filings/{accession_number}/sections/{section_key}", params=params)

    def search_sections(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/sections/search", params=params)

    def offerings(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/offerings", params=params)

    def market_calendar(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/market/calendar", params=params)

    def market_snapshots(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/market/snapshots", params=params)

    def market_bars(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/market/bars", params=params)

    def market_corporate_actions(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/market/corporate-actions", params=params)

    def market_reference(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/market/reference", params=params)

    def market_estimates(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/market/estimates", params=params)

    def news_stories(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/news/stories", params=params)

    def macro_indicators(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/macro/indicators", params=params)

    def macro_releases(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/macro/releases", params=params)

    def macro_calendar(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/macro/calendar", params=params)

    def macro_forecasts(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/macro/forecasts", params=params)

    def factor_catalog(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/catalog", params=params)

    def factor_returns(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/returns", params=params)

    def factor_correlations(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/correlations", params=params)

    def factor_screen(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/screen", params=params)

    def factor_exposures(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/exposures", params=params)

    def factor_decomposition(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/decomposition", params=params)

    def factor_related_stocks(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/related-stocks", params=params)

    def factor_similarity_pack(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/factors/similarity-pack", params=params)

    def portfolio_analyze(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/portfolio/analyze", body=body)

    def portfolio_optimize(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/portfolio/optimize", body=body)

    def portfolio_stress_test(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/portfolio/stress-test", body=body)

    def strategy_factor_rotation(self, body: dict[str, Any] | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/strategies/factor-rotation", body=body or {})

    def strategy_regime_screen(self, body: dict[str, Any] | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/strategies/regime-screen", body=body or {})

    def intelligence_security(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/intelligence/security", params=params)

    def intelligence_company(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/intelligence/company", params=params)

    def intelligence_earnings_preview(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/intelligence/earnings-preview", params=params)

    def intelligence_country_report(self, body: dict[str, Any] | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/intelligence/country-report", body=body or {})

    def intelligence_portfolio(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/intelligence/portfolio", body=body)

    def intelligence_watchlist(self, body: dict[str, Any], **params: Any) -> dict[str, Any]:
        return self._request("POST", "/v1/intelligence/watchlist", params=params, body=body)

    def intelligence_query(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/intelligence/query", body=body)

    def market_indices(self, *, include_inventory: bool | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/market/indices", params={"include_inventory": include_inventory})

    def index_constituents(self, *, index: str | None = None, index_code: str | None = None, cursor: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request(
            "GET",
            "/v1/market/indices/constituents",
            params={"index": index, "index_code": index_code, "cursor": cursor, "limit": limit},
        )

    def volatility_signal(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/signals/volatility", params=params)

    def facts(self, *, tag: str, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/facts", params={"tag": tag, **params})

    def statements(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/statements", params=params)

    def all_statements(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/statements/all", params=params)

    def statement_by_key(self, statement_key: str, **params: Any) -> dict[str, Any]:
        return self._request("GET", f"/v1/statements/{statement_key}", params=params)

    def latest_13f(
        self,
        *,
        cik: str,
        report_date: str | None = None,
        filing_date: str | None = None,
        limit: int | None = None,
    ) -> dict[str, Any]:
        return self._request(
            "GET",
            "/v1/owners/13f",
            params={
                "cik": cik,
                "reportDate": report_date,
                "filingDate": filing_date,
                "limit": limit,
            },
        )

    def compare_13f(self, *, cik: str, limit: int | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/owners/13f/compare", body={"cik": cik, "limit": limit})

    def insiders(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/insiders", params=params)

    def compensation(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/compensation", params=params)

    def compare_compensation(self, *, ticker: str | None = None, cik: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request("POST", "/v1/compensation/compare", body={"ticker": ticker, "cik": cik, "limit": limit})

    def create_artifact(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/artifacts", body=body)

    def list_artifacts(self, *, kind: str | None = None, status: str | None = None, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/artifacts", params={"kind": kind, "status": status, "limit": limit})

    def artifact_summary(self) -> dict[str, Any]:
        return self._request("GET", "/v1/artifacts/summary")

    def get_artifact(self, artifact_id: str) -> dict[str, Any]:
        return self._request("GET", f"/v1/artifacts/{artifact_id}")

    def artifact_manifest(self, artifact_id: str) -> dict[str, Any]:
        return self._request("GET", f"/v1/artifacts/{artifact_id}/manifest")

    def export_artifact(self, artifact_id: str, *, format: str = "json") -> dict[str, Any]:
        return self._request("GET", f"/v1/artifacts/{artifact_id}/export", params={"format": format})

    def download_artifact(self, artifact_id: str) -> dict[str, Any]:
        return self._request("GET", f"/v1/artifacts/{artifact_id}/download")

    def reconcile_artifact(self, artifact_id: str) -> dict[str, Any]:
        return self._request("POST", f"/v1/artifacts/{artifact_id}/reconcile")

    def analytics_query(self, body: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/v1/analytics/query", body=body)

    def list_traces(self, *, ids: str | list[str]) -> dict[str, Any]:
        joined = ",".join(ids) if isinstance(ids, list) else ids
        return self._request("GET", "/v1/traces", params={"ids": joined})

    def get_trace(self, trace_id: str) -> dict[str, Any]:
        return self._request("GET", f"/v1/traces/{trace_id}")

    def segmented_revenues(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/statements/segmented-revenues", params=params)

    def share_float(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/statements/share-float", params=params)

    def board_composition(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/board", params=params)

    def nport_holdings(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/funds/nport/holdings", params=params)

    def latest_risk_categories(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/filings/latest/risk-categories", params=params)

    def beneficial_ownership_reports(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/owners/13d-13g", params=params)

    def institutional_ownership_extract(self, *, cik: str, year: int, quarter: int, limit: int | None = None) -> dict[str, Any]:
        return self._request("GET", "/v1/owners/institutional/extract", params={"cik": cik, "year": year, "quarter": quarter, "limit": limit})

    def ma_events(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/events/ma", params=params)

    def enforcement_actions(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/events/enforcement", params=params)

    def earnings_transcripts(self, **params: Any) -> dict[str, Any]:
        return self._request("GET", "/v1/earnings/transcripts", params=params)

    def mcp_info(self) -> dict[str, Any]:
        return self._request("GET", "/mcp")

    def mcp(self, request: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/mcp", body=request)
