import httpx
import os
import json
import logging
from dataclasses import dataclass
from typing import Any

log = logging.getLogger("mcube.provider")


@dataclass(frozen=True)
class CallResult:
    call_sid: str
    status: str
    provider: str


@dataclass(frozen=True)
class SMSResult:
    message_sid: str
    status: str


class MCubeProvider:
    """
    Minimal MCube REST provider.
    Stores outbound call initiation + hangup + webhook parsing helpers.

    NOTE: For secrets handling, set MCUBE_API_KEY / MCUBE_API_SECRET in env
    (do not hardcode credentials).
    """

    # MCube has multiple click2call endpoints. For your doc, try the superdash variant.
    OUTBOUND_CALLS_URL = os.getenv(
        "MCUBE_OUTBOUND_CALLS_URL",
        "https://config.mcube.com/Restmcube-api/outbound-calls-superdash",
    )

    def __init__(self, *, http_authorization: str):
        """
        MCube Click2Call endpoint expects authentication in HTTP header.

        doc example uses:
        - HTTP header: Authorization: <token>
        - JSON body: custnumber, exenumber, gid, refurl, refid (as applicable)
        """
        self.http_authorization = http_authorization
        # Default to "legacy/minimal" payload style because it matches your existing
        # live_calls/homebook/services/make_calls.py integration.
        self._payload_mode = os.getenv("MCUBE_PAYLOAD_MODE", "legacy").strip().lower()
        self._include_gid_refid = os.getenv("MCUBE_INCLUDE_GID_REFID", "").strip().lower() in (
            "1",
            "true",
            "yes",
        )
        self._include_callback_ws = os.getenv("MCUBE_INCLUDE_CALLBACK_WS", "").strip().lower() in (
            "1",
            "true",
            "yes",
        )

    async def initiate_call(
        self,
        *,
        custnumber: str,
        exenumber: str,
        gid: str,
        refurl: str,
        refid: str,
        callback_url: str | None = None,
        websocket_url: str | None = None,
    ) -> CallResult:
        # MCube is sometimes strict about numeric field types.
        norm_gid = int(gid) if str(gid).isdigit() else gid
        norm_refurl = int(refurl) if str(refurl).isdigit() else refurl

        # Start with minimal payload (legacy mode).
        payload: dict[str, Any] = {
            # MCube examples sometimes use HTTP_AUTHORIZATION in the JSON body.
            # We include both keys for maximum compatibility.
            "HTTP_AUTHORIZATION": self.http_authorization,
            "Authorization": self.http_authorization,
            "custnumber": str(custnumber),
            "exenumber": str(exenumber),
            "refurl": norm_refurl,
        }

        # Optional extensions for endpoint variants that require additional fields.
        if self._payload_mode != "legacy" or self._include_gid_refid:
            payload["gid"] = norm_gid
            payload["refid"] = refid

        if self._include_callback_ws:
            if callback_url:
                payload["callback_url"] = callback_url
            if websocket_url:
                payload["websocket_url"] = websocket_url

        async with httpx.AsyncClient(timeout=httpx.Timeout(10.0)) as client:
            token_present = bool(self.http_authorization)
            token_tail = (self.http_authorization[-4:] if self.http_authorization else "none")
            headers = {}
            if self.http_authorization:
                # Some integrations expect Authorization header; some docs say HTTP_AUTHORIZATION.
                headers["Authorization"] = self.http_authorization
                headers["HTTP_AUTHORIZATION"] = self.http_authorization

            log.info(
                "mcube outbound-call request url=%s exenumber=%s custnumber=%s gid=%s refurl=%s refid=%s auth_present=%s auth_tail=%s",
                self.OUTBOUND_CALLS_URL,
                exenumber,
                custnumber,
                gid,
                refurl,
                refid,
                token_present,
                token_tail,
            )

            # Try JSON first.
            resp = await client.post(self.OUTBOUND_CALLS_URL, json=payload, headers=headers)
            body_text = resp.text
            log.info(
                "mcube outbound-call response status_code=%s body=%s",
                resp.status_code,
                body_text[:2000],
            )

            data: dict[str, Any]
            try:
                data = resp.json()
            except Exception:
                data = {"_raw": body_text}

            # If server complains about invalid request shape, retry as form fields.
            data_msg = ""
            if isinstance(data, dict):
                data_msg = str(data.get("msg") or data.get("message") or "")

            invalid_markers = {
                "invalid request",
                "invalid",
                "bad request",
            }
            looks_invalid = any(m in body_text.lower() for m in invalid_markers)
            # Also handle structured response variants like {"status":false,"msg":"Invalid Request"}.
            if data_msg and "invalid request" in data_msg.lower():
                looks_invalid = True

            if resp.status_code in (400, 415) or looks_invalid or (
                resp.status_code == 500 and not body_text.strip()
            ):
                try:
                    headers_form = dict(headers)
                    headers_form["Content-Type"] = "application/x-www-form-urlencoded"
                    resp_form = await client.post(self.OUTBOUND_CALLS_URL, data=payload, headers=headers_form)
                    body_form = resp_form.text
                    log.info(
                        "mcube outbound-call retry(form) response status_code=%s body=%s",
                        resp_form.status_code,
                        body_form[:2000],
                    )
                    try:
                        data = resp_form.json()
                    except Exception:
                        data = {"_raw": body_form}
                except Exception:
                    log.exception("mcube outbound-call form retry failed")

        # Response mapping (doc varies; handle common keys)
        # MCube "outbound-calls-superdash" commonly returns: {"status":"succ","msg":"...","callid":"<id>"}.
        call_sid = str(
            data.get("callid")
            or data.get("callId")
            or data.get("called")
            or data.get("callSessionId")
            or data.get("call_id")
            or refid
        )
        status = str(data.get("status") or data.get("message") or data.get("msg") or data.get("error") or "")
        return CallResult(call_sid=call_sid, status=status, provider="mcube")

    async def hangup_call(self, *, call_sid: str) -> None:
        # Hangup isn't wired for this Click2Call endpoint yet.
        # MCube typically provides a separate call control API.
        raise NotImplementedError("MCube hangup is not implemented for Restmcube-api click2call yet")

    def parse_webhook(self, payload: dict[str, Any]) -> dict[str, Any]:
        return {
            "call_sid": payload.get("call_id"),
            "status": str(payload.get("status", "")).lower(),
            "duration": payload.get("duration", 0),
            "answered_by": payload.get("answered_by", "human"),
        }

