"""Twilio helper utilities for the Connexity SDK.

This module provides a small async-friendly wrapper around the Twilio REST
client to fetch call metadata (duration, recording URL, start time).

Notes:
- Twilio's API responses can be eventually consistent (e.g., recordings may not
  be immediately available), so recording lookup supports retries on 404.
- These methods do not raise on Twilio errors; they log and return `None`.
"""

from __future__ import annotations

from asyncio import sleep

import aiohttp
from twilio.base.exceptions import TwilioRestException
from twilio.rest import Client

from connexity.utils.logging_config import get_logger

logger = get_logger(__name__)

# Timeout for downloading audio from Twilio recording URLs (seconds)
AUDIO_DOWNLOAD_TIMEOUT = 30


class TwilioCallManager:
    """Convenience wrapper for retrieving Twilio call metadata.

    Args:
        client: An initialized Twilio REST `Client`.

    Attributes:
        client: Stored Twilio client.
        account_sid: Twilio Account SID (derived from the client username).
    """

    def __init__(self, client: Client) -> None:
        self.client: Client = client
        self.account_sid: str = client.username

    async def get_call_duration(self, call_sid: str) -> float | None:
        """Fetch the call duration in seconds.

        On any issue, logs and returns `None`.

        Args:
            call_sid: Twilio call SID.

        Returns:
            The call duration as a float (seconds) if available; otherwise `None`.
        """
        try:
            call = self.client.calls(call_sid).fetch()
            return float(call.duration)
        except TwilioRestException as e:
            logger.warning(f"Error fetching call duration [{e.status}]: {e.msg}")
        except Exception as e:
            logger.error(
                f"Unexpected issue fetching call duration: {e}",
                exc_info=True,
            )
        return None

    async def get_call_recording(
        self, call_sid: str, *, max_retries: int = 5, delay_sec: int = 3
    ) -> tuple[str, bytes, str] | None:
        """Fetch and download the most recent recording for a call.

        Combines the recording URL lookup and the authenticated media
        download into a single retry loop.  Twilio recordings are
        eventually consistent — the metadata may appear before the media
        file is ready — so both steps are retried together.

        Args:
            call_sid: Twilio call SID.
            max_retries: Maximum number of attempts.
            delay_sec: Delay between attempts (seconds).

        Returns:
            A tuple of ``(recording_url, audio_bytes, content_type)`` on
            success, or ``None`` if the recording could not be fetched
            after all retries.
        """
        auth = aiohttp.BasicAuth(
            login=self.client.username,
            password=self.client.password,
        )
        timeout = aiohttp.ClientTimeout(total=AUDIO_DOWNLOAD_TIMEOUT)
        recording_url: str | None = None

        for attempt in range(1, max_retries + 1):
            # --- Step 1: find the recording URL (if not yet known) --------
            if recording_url is None:
                try:
                    recordings = self.client.recordings.list(
                        call_sid=call_sid
                    )
                    if recordings:
                        recording_url = f"{recordings[0].media_url}.wav"
                except TwilioRestException as e:
                    if e.status != 404:
                        logger.warning(
                            "Unexpected issue fetching recording URL: %s", e
                        )
                        return None
                    # 404 → recording metadata not ready yet, will retry

            if recording_url is None:
                if attempt < max_retries:
                    logger.debug(
                        "Recording metadata not ready for %s, "
                        "retrying in %ds (attempt %d/%d)",
                        call_sid,
                        delay_sec,
                        attempt,
                        max_retries,
                    )
                    await sleep(delay_sec)
                    continue
                logger.warning(
                    "Giving up fetching recording for %s after %d attempts",
                    call_sid,
                    max_retries,
                )
                return None

            # --- Step 2: download the media file --------------------------
            try:
                async with (
                    aiohttp.ClientSession(
                        timeout=timeout, auth=auth
                    ) as session,
                    session.get(recording_url) as response,
                ):
                    if response.status == 200:
                        audio_bytes = await response.read()
                        content_type = response.headers.get(
                            "Content-Type", "audio/wav"
                        )
                        logger.debug(
                            "Downloaded Twilio recording: "
                            "size=%d bytes, content_type=%s",
                            len(audio_bytes),
                            content_type,
                        )
                        return (recording_url, audio_bytes, content_type)

                    if response.status == 404 and attempt < max_retries:
                        logger.debug(
                            "Twilio recording media not ready (404), "
                            "retrying in %ds (attempt %d/%d)",
                            delay_sec,
                            attempt,
                            max_retries,
                        )
                        await sleep(delay_sec)
                        continue

                    logger.warning(
                        "Failed to download Twilio recording: "
                        "status=%s, url=%s",
                        response.status,
                        recording_url[:100],
                    )
                    return None

            except (aiohttp.ClientError, TimeoutError) as e:
                if attempt < max_retries:
                    logger.debug(
                        "Network error downloading Twilio recording: "
                        "%s, retrying in %ds (attempt %d/%d)",
                        e,
                        delay_sec,
                        attempt,
                        max_retries,
                    )
                    await sleep(delay_sec)
                    continue
                logger.warning(
                    "Failed to download Twilio recording: %s", e
                )
                return None
            except Exception as e:
                logger.error(
                    f"Unexpected error downloading Twilio recording: {e}",
                    exc_info=True,
                )
                return None

        return None

    async def get_start_call_data(self, call_sid: str) -> str | None:
        """Fetch the call start time as an ISO-8601 string.

        The timestamp is normalized to a `Z` suffix when it is UTC.
        On any issue, logs and returns `None`.

        Args:
            call_sid: Twilio call SID.

        Returns:
            The call start time in ISO-8601 format, or `None` on issue.
        """
        try:
            call = self.client.calls(call_sid).fetch()
            return call.start_time.isoformat().replace("+00:00", "Z")
        except TwilioRestException as e:
            logger.warning(f"Error fetching start time [{e.status}]: {e.msg}")
        except Exception as e:
            logger.error(
                f"Unexpected issue fetching start time: {e}",
                exc_info=True,
            )
        return None

    async def get_call_info(self, call_sid: str) -> dict[str, str] | None:
        """Fetch call information including from and to phone numbers.

        Args:
            call_sid: Twilio call SID.

        Returns:
            A dictionary with 'from', 'to', and 'direction' keys if available;
            otherwise `None`.
        """
        try:
            call = self.client.calls(call_sid).fetch()
            return {
                "from": call._from,
                "to": call.to,
                "direction": call.direction,
            }
        except TwilioRestException as e:
            logger.warning(f"Error fetching call info [{e.status}]: {e.msg}")
        except Exception as e:
            logger.error(
                f"Unexpected issue fetching call info: {e}",
                exc_info=True,
            )
        return None
