"""Configuration used when connecting to a device.

A configuration describes a device, e.g. it's name, IP address and credentials. It is
possible to manually create a configuration, but generally scanning for devices will
provide configurations for you.
"""
from copy import deepcopy
from ipaddress import IPv4Address
from typing import Dict, List, Mapping, Optional

from pyatv.const import PairingRequirement, Protocol
from pyatv.interface import BaseConfig, BaseService, DeviceInfo
from pyatv.support import deprecated


class AppleTV(BaseConfig):
    """Representation of a device configuration.

    An instance of this class represents a single device. A device can have
    several services depending on the protocols it supports, e.g. DMAP or
    AirPlay.
    """

    def __init__(
        self,
        address: IPv4Address,
        name: str,
        deep_sleep: bool = False,
        properties: Optional[Mapping[str, Mapping[str, str]]] = None,
        device_info: Optional[DeviceInfo] = None,
    ) -> None:
        """Initialize a new AppleTV."""
        super().__init__(properties or {})
        self._address = address
        self._name = name
        self._deep_sleep = deep_sleep
        self._services: Dict[Protocol, BaseService] = {}
        self._device_info = device_info or DeviceInfo({})

    @property
    def address(self) -> IPv4Address:
        """IP address of device."""
        return self._address

    @property
    def name(self) -> str:
        """Name of device."""
        return self._name

    @property
    def deep_sleep(self) -> bool:
        """If device is in deep sleep."""
        return self._deep_sleep

    def add_service(self, service: BaseService) -> None:
        """Add a new service.

        If the service already exists, it will be merged.
        """
        existing = self._services.get(service.protocol)
        if existing is not None:
            existing.merge(service)
        else:
            self._services[service.protocol] = service

    def get_service(self, protocol: Protocol) -> Optional[BaseService]:
        """Look up a service based on protocol.

        If a service with the specified protocol is not available, None is
        returned.
        """
        return self._services.get(protocol)

    @property
    def services(self) -> List[BaseService]:
        """Return all supported services."""
        return list(self._services.values())

    @property
    def device_info(self) -> DeviceInfo:
        """Return general device information."""
        return self._device_info

    def __deepcopy__(self, memo) -> "BaseConfig":
        """Return deep-copy of instance."""
        copy = AppleTV(
            self._address,
            self._name,
            self._deep_sleep,
            self._properties,
            self._device_info,
        )
        for service in self.services:
            copy.add_service(deepcopy(service))
        return copy


class ManualService(BaseService):
    """Service used when manually creating and adding a service."""

    def __init__(
        self,
        identifier: Optional[str],
        protocol: Protocol,
        port: int,
        properties: Optional[Mapping[str, str]],
        credentials: Optional[str] = None,
        password: Optional[str] = None,
        requires_password: bool = False,
        pairing_requirement: PairingRequirement = PairingRequirement.Unsupported,
        enabled: bool = True,
    ) -> None:
        """Initialize a new ManualService."""
        super().__init__(
            identifier, protocol, port, properties, credentials, password, enabled
        )
        self._requires_password = requires_password
        self._pairing_requirement = pairing_requirement

    @property
    def requires_password(self) -> bool:
        """Return if a password is required to access service."""
        return self._requires_password

    @property
    def pairing(self) -> PairingRequirement:
        """Return if pairing is required by service."""
        return self._pairing_requirement

    def __deepcopy__(self, memo) -> "BaseService":
        """Return deep-copy of instance."""
        return ManualService(
            self.identifier,
            self.protocol,
            self.port,
            self.properties,
            self.credentials,
            self.password,
            self.requires_password,
            self.pairing,
            self.enabled,
        )


# pylint: disable=too-few-public-methods
class DmapService(ManualService):
    """Representation of a DMAP service.

    **DEPRECATED: Use `pyatv.conf.ManualService` instead.**
    """

    @deprecated
    def __init__(
        self,
        identifier: Optional[str],
        credentials: Optional[str],
        port: int = 3689,
        properties: Optional[Mapping[str, str]] = None,
    ) -> None:
        """Initialize a new DmapService."""
        super().__init__(identifier, Protocol.DMAP, port, properties, credentials)


# pylint: disable=too-few-public-methods
class MrpService(ManualService):
    """Representation of a MediaRemote Protocol (MRP) service.

    **DEPRECATED: Use `pyatv.conf.ManualService` instead.**
    """

    @deprecated
    def __init__(
        self,
        identifier: Optional[str],
        port: int,
        credentials: Optional[str] = None,
        properties: Optional[Mapping[str, str]] = None,
    ) -> None:
        """Initialize a new MrpService."""
        super().__init__(identifier, Protocol.MRP, port, properties, credentials)


# pylint: disable=too-few-public-methods
class AirPlayService(ManualService):
    """Representation of an AirPlay service.

    **DEPRECATED: Use `pyatv.conf.ManualService` instead.**
    """

    @deprecated
    def __init__(
        self,
        identifier: Optional[str],
        port: int = 7000,
        credentials: Optional[str] = None,
        properties: Optional[Mapping[str, str]] = None,
    ) -> None:
        """Initialize a new AirPlayService."""
        super().__init__(identifier, Protocol.AirPlay, port, properties, credentials)


# pylint: disable=too-few-public-methods
class CompanionService(ManualService):
    """Representation of a Companion link service.

    **DEPRECATED: Use `pyatv.conf.ManualService` instead.**
    """

    @deprecated
    def __init__(
        self,
        port: int,
        credentials: Optional[str] = None,
        properties: Optional[Mapping[str, str]] = None,
    ) -> None:
        """Initialize a new CompaniomService."""
        super().__init__(None, Protocol.Companion, port, properties, credentials)


# pylint: disable=too-few-public-methods
class RaopService(ManualService):
    """Representation of an RAOP service.

    **DEPRECATED: Use `pyatv.conf.ManualService` instead.**
    """

    @deprecated
    def __init__(
        self,
        identifier: Optional[str],
        port: int = 7000,
        credentials: Optional[str] = None,
        password: Optional[str] = None,
        properties: Optional[Mapping[str, str]] = None,
    ) -> None:
        """Initialize a new RaopService."""
        super().__init__(
            identifier, Protocol.RAOP, port, properties, credentials, password=password
        )
