"""Authentication session management."""

import asyncio
import time
from typing import Any, Optional

import aiohttp

from .errors import (
    AuthenticationError,
    InvalidResponseError,
    NetworkError,
    TimeoutError,
)
from .types import (
    AuthResult,
    AuthStatus,
    Credentials,
    PollOptions,
    StatusResponse,
)

DEFAULT_POLL_INTERVAL = 2.0  # seconds
DEFAULT_TIMEOUT = 300.0  # 5 minutes


class AuthSession:
    """Represents an active authentication session.

    Use this to get the redirect URL and wait for completion.

    Attributes:
        id: Unique identifier for this session.
        state_key: State key for the OAuth flow.
        redirect_url: URL to redirect the user to for OAuth authorization.
    """

    def __init__(
        self,
        *,
        id: str,
        state_key: str,
        redirect_url: str,
        base_url: str,
        headers: dict[str, str],
        connector_id: str,
    ) -> None:
        """Initialize an AuthSession.

        Args:
            id: Unique identifier for this session.
            state_key: State key for the OAuth flow.
            redirect_url: URL to redirect the user to for OAuth authorization.
            base_url: Base URL for the Fastn API.
            headers: HTTP headers for API requests.
            connector_id: The connector ID.
        """
        self.id = id
        self.state_key = state_key
        self.redirect_url = redirect_url
        self._base_url = base_url
        self._headers = headers
        self._connector_id = connector_id

    async def wait_for_completion(
        self,
        options: Optional[PollOptions] = None,
    ) -> AuthResult:
        """Poll for the authentication status and wait for completion.

        Resolves when status becomes ACTIVE or FAILED.

        Args:
            options: Polling options (interval and timeout).

        Returns:
            The authentication result with status and credentials.

        Raises:
            TimeoutError: If the timeout is reached.
            AuthenticationError: If the authentication fails.
        """
        opts = options or PollOptions()
        interval = opts.interval
        timeout = opts.timeout

        start_time = time.monotonic()

        while True:
            elapsed = time.monotonic() - start_time
            if elapsed > timeout:
                raise TimeoutError(f"Authentication timed out after {timeout}s")

            status = await self.get_status()

            if status.status == AuthStatus.ACTIVE:
                credentials = await self._get_credentials()
                return AuthResult(
                    status=AuthStatus.ACTIVE,
                    credentials=credentials,
                )

            if status.status == AuthStatus.FAILED:
                raise AuthenticationError(
                    status.error_message or "Authentication failed"
                )

            # Status is INACTIVE, continue polling
            await asyncio.sleep(interval)

    async def get_status(self) -> StatusResponse:
        """Get the current status of the authentication session.

        Returns:
            The current status response.

        Raises:
            NetworkError: If the request fails.
            InvalidResponseError: If the response is invalid.
        """
        url = f"{self._base_url}/connect/status/{self.id}"

        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=self._headers) as response:
                if not response.ok:
                    raise NetworkError(
                        f"Failed to get status: {response.reason}",
                        status_code=response.status,
                    )

                data: dict[str, Any] = await response.json()

                if not data or "status" not in data:
                    raise InvalidResponseError("Invalid status response")

                return StatusResponse(
                    status=AuthStatus(data["status"]),
                    error_message=data.get("errorMessage"),
                )

    async def _get_credentials(self) -> Credentials:
        """Get the credentials after successful activation.

        Returns:
            The connector credentials.

        Raises:
            NetworkError: If the request fails.
        """
        url = f"{self._base_url}/connect/credentials/{self._connector_id}"

        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=self._headers) as response:
                if not response.ok:
                    raise NetworkError(
                        f"Failed to get credentials: {response.reason}",
                        status_code=response.status,
                    )

                data: dict[str, Any] = await response.json()
                return Credentials.from_dict(data)
