import asyncio
import aiohttp

import xml.etree.ElementTree as ET
import time

from .base import BaseController
from .schemas import PTZPosition, RCChannels
from .utils import MOVEMENT_MAP


class AsyncHikvision(BaseController):
    """Асинхронный PTZ контроллер с поддержкой RC-каналов"""

    def __init__(
            self,
            ip: str,
            username: str = "admin",
            password: str = "kluz5056",
            port: int = 80,
            channel: int = 1,
            home_pos: list[float] | None = None,
            update_interval: float = 0.5
    ):
        super().__init__(ip, port, home_pos)
        self.__username = username
        self.__password = password
        self.channel = channel
        self.update_interval = update_interval

        # Состояние камеры
        self._position = PTZPosition()
        self._rc_channels = RCChannels()
        self._last_rc_channels = RCChannels()
        self._is_moving = False
        self._move_task: asyncio.Task | None = None

        # HTTP клиент
        self._session: aiohttp.ClientSession | None = None
        self._auth: aiohttp.BasicAuth | None = None

        # Флаги
        self._running = False
        self._update_task: asyncio.Task | None = None

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.disconnect()

    async def connect(self) -> bool:
        """Подключение к камере"""
        try:
            self._auth = aiohttp.DigestAuthMiddleware(login=self.__username, password=self.__password)
            self._session = aiohttp.ClientSession(middlewares=(self._auth,))

            test_url = f"http://{self.ip}:{self.port}/ISAPI/System/status"
            async with self._session.get(test_url) as response:
                if response.status == 200:
                    self._connection = True
                    self._running = True

                    self._update_task = asyncio.create_task(self._position_updater())
                    self.logger.info(f"Подключено к камере {self.ip}")
                    return True

        except Exception as e:
            self.logger.error(f"Ошибка подключения: {e}")
            self._connection = False

        return False

    async def disconnect(self):
        """Отключение от камеры"""
        self._running = False

        if self._update_task:
            self._update_task.cancel()
            try:
                await self._update_task
            except asyncio.CancelledError:
                pass

        if self._move_task and not self._move_task.done():
            self._move_task.cancel()

        if self._session:
            await self._session.close()

        self._connection = False
        self.logger.info("Отключено от камеры")

    @property
    def connection(self) -> bool:
        return self._connection

    @property
    def position(self) -> list[float]:
        return [self._position.tilt, self._position.pan, self._position.zoom]

    async def _send_request(
            self,
            method: str,
            endpoint: str,
            params: dict[str, object] | None = None,
            data: str | None = None,
            headers: dict[str, str] | None = None
    ) -> str | None:
        """Отправка HTTP запроса к камере"""
        if not self._session:
            return None

        url = f"http://{self.ip}:{self.port}{endpoint}"

        try:
            async with self._session.request(
                    method=method,
                    url=url,
                    params=params,
                    data=data,
                    headers=headers,
                    timeout=aiohttp.ClientTimeout(total=5)
            ) as response:

                if response.status == 200:
                    return await response.text()
                else:
                    self.logger.error(f"HTTP ошибка {response.status}: {await response.text()}")

        except Exception as e:
            self.logger.error(f"Ошибка запроса: {e}")
            self._connection = False

        return None

    async def goto_absolute(self, position: PTZPosition) -> bool:
        """
        Абсолютное позиционирование

        Args:
            position: целевая позиция PTZ
        """
        xml_data = f"""<?xml version="1.0" encoding="UTF-8"?>
<PTZData version="2.0" xmlns="http://www.hikvision.com/ver20/XMLSchema">
    <AbsoluteHigh>
        <elevation>{position.tilt}</elevation>
        <azimuth>{position.pan}</azimuth>
        <absoluteZoom>{position.zoom}</absoluteZoom>
    </AbsoluteHigh>
</PTZData>"""

        response = await self._send_request(
            method="PUT",
            endpoint=f"/ISAPI/PTZCtrl/channels/{self.channel}/absolute",
            data=xml_data,
            headers={"Content-Type": "application/xml"}
        )

        return response is not None

    async def continuous_move(
            self,
            pan_speed: int = 50,
            tilt_speed: int = 50,
            zoom_speed: int = 50
    ) -> bool:
        """
        Непрерывное движение

        Args:
            pan_speed: скорость панорамирования (1-100)
            tilt_speed: скорость наклона (1-100)
            zoom_speed: скорость зума (1-100)
        """
        xml_data = f"""<?xml version="1.0" encoding="UTF-8"?>
        <PTZData version="2.0" xmlns="http://www.hikvision.com/ver20/XMLSchema">
            <Continuous>
                <speed>
                    <pan>{pan_speed}</pan>
                    <tilt>{tilt_speed}</tilt>
                    <zoom>{zoom_speed}</zoom>
                </speed>
            </Continuous>
        </PTZData>"""

        response = await self._send_request(
            method="PUT",
            endpoint=f"/ISAPI/PTZCtrl/channels/{self.channel}/continuous",
            data=xml_data,  # Отправляем XML
            headers={"Content-Type": "application/xml"}
        )
        return response is not None

    async def _parse_position_xml(self, xml_text: str) -> PTZPosition | None:
        """Парсинг XML с позицией камеры"""
        try:
            root = ET.fromstring(xml_text)

            # Ищем позицию в разных форматах
            namespaces = {'ns': 'http://www.hikvision.com/ver20/XMLSchema'}

            # Формат AbsoluteHigh
            elevation_elem = root.find('.//ns:elevation', namespaces)
            azimuth_elem = root.find('.//ns:azimuth', namespaces)
            zoom_elem = root.find('.//ns:absoluteZoom', namespaces)

            if elevation_elem is not None and azimuth_elem is not None:
                return PTZPosition(
                    pan=int(azimuth_elem.text),
                    tilt=int(elevation_elem.text),
                    zoom=int(zoom_elem.text) if zoom_elem is not None else 1
                )

        except Exception as e:
            self.logger.error(f"Ошибка парсинга позиции: {e}")

        return None

    async def _position_updater(self):
        """Фоновое обновление позиции камеры"""
        while self._running:
            try:
                response = await self._send_request(
                    method="GET",
                    endpoint=f"/ISAPI/PTZCtrl/channels/{self.channel}/status"
                )

                if response:
                    position = await self._parse_position_xml(response)
                    if position:
                        self._position = position
                        self._last_update = time.time()

                await asyncio.sleep(self.update_interval)

            except asyncio.CancelledError:
                break
            except Exception as e:
                self.logger.error(f"Ошибка в position_updater: {e}")
                await asyncio.sleep(1)

    async def rc_control(
            self,
            channel1: int,
            channel2: int,
            channel3: int,
            channel4: int
    ):

        key = (channel3, channel4, channel1)

        if key in MOVEMENT_MAP:
            pan_speed, tilt_speed, zoom_speed = MOVEMENT_MAP[key]
            await self.continuous_move(
                pan_speed=pan_speed,
                tilt_speed=tilt_speed,
                zoom_speed=zoom_speed
            )
            self._is_moving = True
        elif self._is_moving:
            if channel3 == 1500 and channel4 == 1500 and channel1 == 1500:
                await self.continuous_move(0, 0, 0)
                self._is_moving = False

    async def goto(self, pan: int, tilt: int, zoom: int):
        position = PTZPosition(pan, tilt, zoom)
        self.logger.info("goto", s=position)
        return await self.goto_absolute(position)

    async def set_preset(self, preset_id: int, name: str = "") -> bool:
        """Создание пресета"""
        params = {'id': preset_id}
        if name:
            params['name'] = name

        response = await self._send_request(
            method="POST",
            endpoint=f"/ISAPI/PTZCtrl/channels/{self.channel}/presets",
            params=params
        )

        return response is not None

    async def goto_preset(self, preset_id: int) -> bool:
        """Переход к пресету"""
        response = await self._send_request(
            method="PUT",
            endpoint=f"/ISAPI/PTZCtrl/channels/{self.channel}/presets/{preset_id}/goto"
        )

        return response is not None

    async def stop(self):
        """Остановка всех движений"""
        await self.continuous_move(0, 0, 0)
        self._is_moving = False

    async def reset_rc(self):
        """Сброс RC-каналов в нейтральное положение"""
        self._rc_channels = RCChannels()
        await self.stop()

    async def go_home(self):
        """Переход в домашнюю позицию"""
        self.logger.info("go_home", s=self.home_position)
        return await self.goto_absolute(self.home_position)

    async def reboot(self):
        """Перезагрузка камеры"""
        response = await self._send_request(
            method="PUT",
            endpoint="/ISAPI/System/reboot"
        )
        return response is not None


