"""Async client for the SolaX PocketWiFi dongle HTTP API."""

from __future__ import annotations

from typing import Self

import aiohttp

from .const import DEFAULT_PORT, DEFAULT_TIMEOUT
from .exceptions import (
    SolaxAllZeroError,
    SolaxAuthenticationError,
    SolaxConnectionError,
    SolaxError,
    SolaxResponseError,
    SolaxTimeoutError,
)
from .models import InverterData, InverterInfo
from .parsers import get_parser

REQUEST_TIMEOUT = aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT)


class SolaxClient:
    """Async client for the SolaX PocketWiFi dongle HTTP API.

    Can be used as a context manager or standalone. When used standalone,
    pass an existing aiohttp.ClientSession to avoid creating a new one.
    """

    def __init__(
        self,
        host: str,
        password: str,
        *,
        port: int = DEFAULT_PORT,
        username: str | None = None,
        session: aiohttp.ClientSession | None = None,
    ) -> None:
        self._host = host
        self._port = port
        self._password = password
        self._username = username
        self._session = session
        self._owns_session = session is None

    async def __aenter__(self) -> Self:
        if self._owns_session:
            self._session = aiohttp.ClientSession(timeout=REQUEST_TIMEOUT)
        return self

    async def __aexit__(self, *args: object) -> None:
        if self._owns_session and self._session:
            await self._session.close()
            self._session = None

    async def close(self) -> None:
        """Close the session if we own it."""
        if self._owns_session and self._session:
            await self._session.close()
            self._session = None

    async def _post(self) -> dict:
        """Make a POST request to the dongle."""
        if self._session is None:
            msg = "Session not initialized. Use as context manager or pass a session."
            raise SolaxError(msg)

        url = f"http://{self._host}:{self._port}/"
        body = f"optType=ReadRealTimeData&pwd={self._password}"
        headers = {"Content-Type": "application/x-www-form-urlencoded"}

        kwargs: dict = {
            "data": body,
            "headers": headers,
            "timeout": REQUEST_TIMEOUT,
        }
        if self._username is not None:
            kwargs["auth"] = aiohttp.BasicAuth(self._username, self._password)

        try:
            async with self._session.post(url, **kwargs) as resp:
                if resp.status == 401:
                    msg = "Invalid credentials"
                    raise SolaxAuthenticationError(msg)
                if resp.status != 200:
                    msg = f"Dongle returned HTTP {resp.status}"
                    raise SolaxResponseError(msg)
                return await resp.json(content_type=None)
        except (SolaxAuthenticationError, SolaxResponseError):
            raise
        except TimeoutError as err:
            msg = f"Timeout connecting to {url}"
            raise SolaxTimeoutError(msg) from err
        except aiohttp.ClientError as err:
            msg = f"Error connecting to {url}"
            raise SolaxConnectionError(msg) from err

    @staticmethod
    def _validate_response(data: dict) -> None:
        """Validate the dongle response structure and data."""
        if "Data" not in data or "type" not in data:
            msg = f"Invalid response structure: missing required fields"
            raise SolaxResponseError(msg)

        raw_data = data["Data"]
        if not isinstance(raw_data, list) or not raw_data:
            msg = "Invalid response: Data is not a non-empty list"
            raise SolaxResponseError(msg)

        if all(v == 0 for v in raw_data):
            raise SolaxAllZeroError("Data array is all zeros — inverter may be offline")

    @staticmethod
    def _parse_info(data: dict) -> InverterInfo:
        """Parse inverter info from the response."""
        info = data.get("Information", [])
        return InverterInfo(
            serial_number=info[2] if len(info) > 2 else data.get("sn", ""),
            inverter_type=data["type"],
            rated_power=float(info[0]) if info else 0.0,
            firmware_main=str(info[1]) if len(info) > 1 else "",
            firmware_sub=data.get("ver", ""),
        )

    async def get_data(self) -> tuple[InverterInfo, InverterData]:
        """Fetch and parse real-time data from the inverter.

        Returns:
            Tuple of (InverterInfo, InverterData).

        Raises:
            SolaxError: On connection, auth, or data errors.
        """
        response = await self._post()
        self._validate_response(response)

        inverter_type = response["type"]
        parser = get_parser(inverter_type)
        if parser is None:
            msg = f"Unsupported inverter type: {inverter_type}"
            raise SolaxResponseError(msg)

        info = self._parse_info(response)
        inverter_data = parser.parse(response["Data"])

        return info, inverter_data
