"""A fake AirPlay device."""

import plistlib
import binascii
import logging
from collections import namedtuple

from aiohttp import web

from pyatv.support.net import unused_port

_LOGGER = logging.getLogger(__name__)


# --- START AUTHENTICATION DATA VALID SESSION (FROM DEVICE) ---

DEVICE_IDENTIFIER = "75FBEEC773CFC563"
DEVICE_AUTH_KEY = "8F06696F2542D70DF59286C761695C485F815BE3D152849E1361282D46AB1493"
DEVICE_PIN = 2271
DEVICE_CREDENTIALS = DEVICE_IDENTIFIER + ":" + DEVICE_AUTH_KEY

# pylint: disable=E501
# For authentication
_DEVICE_AUTH_STEP1 = b"62706c6973743030d201020304566d6574686f6454757365725370696e5f101037354642454543373733434643353633080d14191d0000000000000101000000000000000500000000000000000000000000000030"  # noqa
_DEVICE_AUTH_STEP1_RESP = b"62706c6973743030d20102030452706b5473616c744f1101008817e16146c7d12b45e810b0bf190a4ccb25d9a20a8d0504d874daa8db5574c51c8b33703a95c00bdbe99c8c3745d1ef1b38e538edfd98e09ec029effe6f28b3b54a1bd41c28d8f33da6f5ac9327bfce9a66869dae645b5cbd2c6b8fbe14a30ad4f8598154f2ef7f4f52cee3e3042a69780463c26bbb764870eb1995b26a2a4ade05564836d788baf07469a143c410ea9d07a068eb790b2b0aa5b86c990636814e3fa1a899ceba1af45b211ca4bd3b5b66ffaf16051a4f851e120476054258f257b8521a068907ad5e9c7220d5cef9aa072dec9edb7ebf633cad4d52d105cf58440f17e236332b0b26539851a879e9ac8d3c2da4c590785468e590296d39d7374f1010fca6dcb6b83a7c716a692f806e9159540008000d001000150119000000000000020100000000000000050000000000000000000000000000012c"  # noqa
_DEVICE_AUTH_STEP2 = b"62706c6973743030d20102030452706b5570726f6f664f1101000819b6ba7feead4753809314e2b4c5db9109f737a0fc70b758342b6bbf536fae4e40cf94607588abb17c2076030cc00c2c1fa5fc3b3dfe8aa1ec2f23f74d917c0792fbf02f131377dfb8ae2a1656ceaa0a36bb3ab752586e1af17e1d5ef24ce083f3f9298d0be761f26c0d48af86510bf9aac7940cf90bff6bd214cf34b5536856c80f076cfbe06fd69af9d6a07a6d3ac580dfffc8a40b9730575a16c5046cd73321a944880dcf9fac952afc7ffd2d135e57ec208b11cef22b734f331ad4d8c9a737b588f7b30bd5210c65cae2ba0226f69ce7b505771faa63af89ed2f9e8325d7d5f3a2da7412f9d837860632d7f81b7fa5e09dd85e1539184070c0fa8433c24f1014fc6286910833d3e7ae0631d47ddbb0f492ef85b80008000d00100016011a0000000000000201000000000000000500000000000000000000000000000131"  # noqa
_DEVICE_AUTH_STEP2_RESP = b"62706c6973743030d101025570726f6f664f101484a88548b12bce122ad1cea6caff312630edcf27080b110000000000000101000000000000000300000000000000000000000000000028"  # noqa
_DEVICE_AUTH_STEP3 = b"62706c6973743030d20102030457617574685461675365706b4f101052a92f8712c6ea417f3adb3d03d8e5634f1020ff07fc8520d10728e6f2ab0a0245dfa20709b5d1ae5f9a19328b0663ba9414f2080d15192c000000000000010100000000000000050000000000000000000000000000004f"  # noqa
_DEVICE_AUTH_STEP3_RESP = b"62706c6973743030d2010203045365706b57617574685461674f10206285b20afad4cefe1fce40cee685ab072c75240cb47fb71bc3b3d03dca52dc5d4f1010893eb8e5ae418b245e9b1bf7cba9116b080d11193c000000000000010100000000000000050000000000000000000000000000004f"  # noqa

# For verification
_DEVICE_VERIFY_STEP1 = b"01000000891bae9f581f68f9c9933c4f713fbb5b9de639ec7df5d0a4fd4f342f1c21aa6a5e9d1e843302d6265b8c48dd169e273460e567916b0b36280ac071001118f6b2"  # noqa
_DEVICE_VERIFY_STEP1_RESP = b"3221371da9f00d035955caa912455fd2acee68117b557f25e39168746af4b631cfab7b2c6d0b58e96cc10af884f5a4cdef8063858a9d9c04e866743cf4b77b4be50de1352ab4ff2691a1a7afd8c1341475b4170ac50455973b7fcf3c24324fa9"  # noqa
_DEVICE_VERIFY_STEP2 = b"00000000a1f91acf64aacb185684080b817103b423816ad63b7f5e001f62337b4cc4b3b92c1474959930b7c2a59d0004814300580459d06fc6cc6441bd82bac72a5c5cc7"  # noqa
_DEVICE_VERIFY_STEP2_RESP = b""  # Value not used by pyatv

# pylint: enable=E501
# --- END AUTHENTICATION DATA ---

AirPlayPlaybackResponse = namedtuple("AirPlayPlaybackResponse", "code content")


class FakeAirPlayState:
    def __init__(self):
        self.airplay_responses = []
        self.has_authenticated = True
        self.always_auth_fail = False
        self.last_airplay_url = None
        self.last_airplay_start = None
        self.last_airplay_uuid = None
        self.play_count = 0
        self.injected_play_fails = 0


class FakeAirPlayService:
    def __init__(self, state, app, loop):
        self.state = state
        self.port = None
        self.app = app
        self.runner = None

        self.app.router.add_post("/play", self.handle_airplay_play)
        self.app.router.add_get("/playback-info", self.handle_airplay_playback_info)
        self.app.router.add_post("/pair-pin-start", self.handle_pair_pin_start)
        self.app.router.add_post("/pair-setup-pin", self.handle_pair_setup_pin)
        self.app.router.add_post("/pair-verify", self.handle_airplay_pair_verify)

    async def start(self, start_web_server):
        if start_web_server:
            self.port = unused_port()
            self.runner = web.AppRunner(self.app)
            await self.runner.setup()
            site = web.TCPSite(self.runner, "0.0.0.0", self.port)
            await site.start()

    async def cleanup(self):
        if self.runner:
            await self.runner.cleanup()

    async def handle_airplay_play(self, request):
        """Handle AirPlay play requests."""

        self.state.play_count += 1

        if self.state.always_auth_fail or not self.state.has_authenticated:
            return web.Response(status=503)
        if self.state.injected_play_fails > 0:
            self.state.injected_play_fails -= 1
            return web.Response(status=500)

        headers = request.headers

        # Verify headers first
        assert headers["User-Agent"] == "MediaControl/1.0"
        assert headers["Content-Type"] == "application/x-apple-binary-plist"

        body = await request.read()
        parsed = plistlib.loads(body)

        self.state.last_airplay_url = parsed["Content-Location"]
        self.state.last_airplay_start = parsed["Start-Position"]
        self.state.last_airplay_uuid = parsed["X-Apple-Session-ID"]

        return web.Response(status=200)

    async def handle_airplay_playback_info(self, request):
        """Handle AirPlay playback-info requests."""
        if self.state.airplay_responses:
            response = self.state.airplay_responses.pop()
        else:
            plist = dict(readyToPlay=False, uuid=123)
            response = AirPlayPlaybackResponse(200, plistlib.dumps(plist))
        return web.Response(body=response.content, status=response.code)

    # TODO: Extract device auth code to separate module and make it more
    # general. This is a dumb implementation that verifies hard coded values,
    # which is fine for regression but an implementation with better validation
    # would be better.
    async def handle_pair_pin_start(self, request):
        """Handle start of AirPlay device authentication."""
        return web.Response(status=200)  # Normally never fails

    async def handle_pair_setup_pin(self, request):
        """Handle AirPlay device authentication requests."""
        content = await request.content.read()
        hexlified = binascii.hexlify(content)

        if hexlified == _DEVICE_AUTH_STEP1:
            return web.Response(
                body=binascii.unhexlify(_DEVICE_AUTH_STEP1_RESP), status=200
            )
        elif hexlified == _DEVICE_AUTH_STEP2:
            return web.Response(
                body=binascii.unhexlify(_DEVICE_AUTH_STEP2_RESP), status=200
            )
        elif hexlified == _DEVICE_AUTH_STEP3:
            return web.Response(
                body=binascii.unhexlify(_DEVICE_AUTH_STEP3_RESP), status=200
            )

        return web.Response(status=503)

    async def handle_airplay_pair_verify(self, request):
        """Handle verification of AirPlay device authentication."""
        content = await request.content.read()
        hexlified = binascii.hexlify(content)

        if hexlified == _DEVICE_VERIFY_STEP1:
            return web.Response(
                body=binascii.unhexlify(_DEVICE_VERIFY_STEP1_RESP), status=200
            )
        elif hexlified == _DEVICE_VERIFY_STEP2:
            self.state.has_authenticated = True
            return web.Response(body=_DEVICE_VERIFY_STEP2_RESP, status=200)

        return web.Response(body=b"", status=503)


class FakeAirPlayUseCases:
    """Wrapper for altering behavior of a FakeAirPlayDevice instance."""

    def __init__(self, state):
        """Initialize a new AirPlayUseCases."""
        self.state = state

    def airplay_play_failure(self, count):
        """Make play command fail a number of times."""
        self.state.injected_play_fails = count

    def airplay_playback_idle(self):
        """Make playback-info return idle info."""
        plist = dict(readyToPlay=False, uuid=123)
        self.state.airplay_responses.insert(
            0, AirPlayPlaybackResponse(200, plistlib.dumps(plist))
        )

    def airplay_playback_playing(self):
        """Make playback-info return that something is playing."""
        # This is _not_ complete, currently not needed
        plist = dict(duration=0.8)
        self.state.airplay_responses.insert(
            0, AirPlayPlaybackResponse(200, plistlib.dumps(plist))
        )

    def airplay_require_authentication(self):
        """Require device authentication for AirPlay."""
        self.state.has_authenticated = False

    def airplay_always_fail_authentication(self):
        """Always fail authentication for AirPlay."""
        self.state.always_auth_fail = True

    def airplay_playback_playing_no_permission(self):
        """Make playback-info return forbidden."""
        self.state.airplay_responses.insert(0, AirPlayPlaybackResponse(403, None))