import asyncio
from abc import ABC, abstractmethod
from typing import Any, Dict

from aiohttp import ClientSession, ClientTimeout, ClientError

from .consts import (
    API_ENDPOINT,
    API_TIMEOUT,
    SDK_LOGGER,
    WiimHttpCommand,
)
from .exceptions import WiimRequestException, WiimInvalidDataException


class WiimBaseEndpoint(ABC):
    """Represents an abstract WiiM endpoint."""

    logger = SDK_LOGGER

    @abstractmethod
    async def request(self, command: str) -> None:
        """Performs a request on the given command and verifies the result (e.g., expects 'OK')."""

    @abstractmethod
    async def json_request(self, command: str) -> Dict[str, Any]:
        """Performs a request on the given command and returns the result as a JSON object."""

    @abstractmethod
    def to_dict(self) -> dict[str, str]:
        """Return the state of the WiimBaseEndpoint"""

    @abstractmethod
    def __str__(self) -> str:
        """String representation of the endpoint."""


class WiimApiEndpoint(WiimBaseEndpoint):
    """Represents a WiiM HTTP API endpoint."""

    def __init__(
        self,
        *,
        protocol: str,
        port: int,
        endpoint: str,
        session: ClientSession,
        verify_ssl: bool = True,
        owns_session: bool = False,
    ):
        assert protocol in [
            "http",
            "https",
        ], "Protocol must be either 'http' or 'https'"

        include_port = (protocol == "http" and port != 80) or (
            protocol == "https" and port != 443
        )
        port_suffix = f":{port}" if include_port else ""
        self._base_url: str = f"{protocol}://{endpoint}{port_suffix}"
        self._session: ClientSession = session
        self._owns_session = owns_session
        self._request_kwargs: dict[str, Any] = {}
        if protocol == "https" and not verify_ssl:
            self._request_kwargs["ssl"] = False
        self.logger.debug("WiimApiEndpoint initialized for %s", self._base_url)

    def to_dict(self) -> dict[str, str]:
        """Return the state of the WiimApiEndpoint"""
        return {"base_url": self._base_url}

    async def request(self, command: str) -> None:
        """
        Performs a GET request on the given command and verifies the result.
        Expects the response text to be "OK".
        """
        url = API_ENDPOINT.format(self._base_url, command)
        self.logger.debug(f"Requesting (expect OK): {url}")
        try:
            async with self._session.get(
                url,
                timeout=ClientTimeout(total=API_TIMEOUT),
                **self._request_kwargs,
            ) as response:
                response_text = await response.text()
                self.logger.debug(
                    f"Response from {url}: {response.status} {response_text[:100]}"
                )  # Log snippet
                if response.status != 200:
                    raise WiimRequestException(
                        f"Request to {url} failed with status {response.status}: {response_text}"
                    )
                txt = response_text.strip().upper()
                if "OK" not in txt:
                    raise WiimInvalidDataException(
                        f"Request to {url} did not return 'OK'. Got: '{response_text}'"
                    )
        except ClientError as err:
            self.logger.warning(f"ClientError for {url}: {err}")
            raise WiimRequestException(f"Network error for {url}: {err}") from err
        except asyncio.TimeoutError:
            self.logger.warning(f"Timeout for {url}")
            raise WiimRequestException(f"Timeout for {url}") from asyncio.TimeoutError

    async def json_request(self, command: str) -> Dict[str, Any]:
        """
        Performs a GET request on the given command and returns the result as a JSON object.
        """
        url = API_ENDPOINT.format(self._base_url, command)
        self.logger.debug(f"Requesting (expect JSON): {url}")
        try:
            async with self._session.get(
                url,
                timeout=ClientTimeout(total=API_TIMEOUT),
                **self._request_kwargs,
            ) as response:
                response_text = await response.text()
                self.logger.debug(
                    f"Response from {url}: {response.status} {response_text[:200]}"
                )
                if response.status != 200:
                    raise WiimRequestException(
                        f"Request to {url} failed with status {response.status}: {response_text}"
                    )
                try:
                    if (
                        command == WiimHttpCommand.DEVICE_STATUS
                        or command == WiimHttpCommand.PLAYER_STATUS
                        or command == WiimHttpCommand.AUDIO_OUTPUT_HW_MODE
                        or command == WiimHttpCommand.MULTIROOM_LIST
                    ):
                        if response_text.strip().startswith(
                            "{"
                        ) and response_text.strip().endswith("}"):
                            return await response.json(content_type=None)

                        data: Dict[str, Any] = {}
                        for line in response_text.strip().split("\n"):
                            if "=" in line:
                                key, value = line.split("=", 1)
                                data[key.strip()] = value.strip()
                            elif line.strip():
                                data[line.strip()] = True
                        if not data and response_text.strip():
                            if response_text.strip().upper() == "FAILED":
                                raise WiimInvalidDataException(
                                    f"Command {command} returned 'Failed'",
                                    data=response_text,
                                )
                            self.logger.warning(
                                "Unexpected format for %s, treating as simple string: %s",
                                command,
                                response_text,
                            )
                            return {"response": response_text.strip()}
                        return data
                    else:
                        return await response.json(content_type=None)

                except ValueError as err:
                    self.logger.warning(
                        f"Failed to parse JSON/Key-Value from {url}: {err}. Response text: {response_text[:200]}"
                    )
                    raise WiimInvalidDataException(
                        f"Invalid JSON or Key-Value response from {url}: {response_text}",
                        data=response_text,
                    ) from err
        except ClientError as err:
            self.logger.warning(f"ClientError for {url}: {err}")
            raise WiimRequestException(f"Network error for {url}: {err}") from err
        except asyncio.TimeoutError:
            self.logger.warning(f"Timeout for {url}")
            raise WiimRequestException(f"Timeout for {url}") from asyncio.TimeoutError

    def __str__(self) -> str:
        return self._base_url

    async def async_close(self) -> None:
        """Close the underlying aiohttp session if this endpoint owns it."""
        if self._owns_session and not self._session.closed:
            await self._session.close()
