"""Reolink Camera Media Source Implementation."""
import datetime as dt
import logging
import os
import secrets
from typing import Dict, List, Optional, Tuple
from urllib.parse import quote_plus, unquote_plus
from aiohttp import web

from dateutil import relativedelta
from homeassistant.components.http.const import KEY_AUTHENTICATED

# from homeassistant.components.http.auth import async_sign_path

# from homeassistant.components.http import current_request
# from homeassistant.components.http.const import KEY_HASS_REFRESH_TOKEN_ID

from homeassistant.core import HomeAssistant, callback

import homeassistant.util.dt as dt_utils

from homeassistant.components.http import HomeAssistantView

from homeassistant.components.media_player.errors import BrowseError
from homeassistant.components.media_player.const import (
    MEDIA_CLASS_DIRECTORY,
    MEDIA_CLASS_VIDEO,
    MEDIA_TYPE_VIDEO,
)
from homeassistant.components.media_source.const import MEDIA_MIME_TYPES
from homeassistant.components.media_source.error import MediaSourceError, Unresolvable
from homeassistant.components.media_source.models import (
    BrowseMediaSource,
    MediaSource,
    MediaSourceItem,
    PlayMedia,
)

from homeassistant.components.stream import create_stream

from homeassistant.helpers.event import async_call_later

from .base import ReolinkBase, searchtime_to_datetime

# from . import typings

from .const import (
    BASE,
    DOMAIN,
    DOMAIN_DATA,
    LONG_TOKENS,
    MEDIA_SOURCE,
    SHORT_TOKENS,
    THUMBNAIL_EXTENSION as EXTENSION,
    THUMBNAIL_URL,
    VOD_URL,
)

_LOGGER = logging.getLogger(__name__)
# MIME_TYPE = "rtmp/mp4"
# MIME_TYPE = "video/mp4"
MIME_TYPE = "application/x-mpegURL"

NAME = "Reolink IP Camera"

STORAGE_VERSION = 1


class IncompatibleMediaSource(MediaSourceError):
    """Incompatible media source attributes."""


async def async_get_media_source(hass: HomeAssistant):
    """Set up Reolink media source."""

    _LOGGER.debug("Creating REOLink Media Source")
    source = ReolinkMediaSource(hass)
    hass.http.register_view(ReolinkSourceThumbnailView(hass))
    hass.http.register_view(ReolinkSourceVODView(hass))

    return source


class ReolinkMediaSource(MediaSource):
    """Provide Reolink camera recordings as media sources."""

    name: str = NAME

    def __init__(self, hass: HomeAssistant):
        """Initialize Reolink source."""
        super().__init__(DOMAIN)
        self.hass = hass
        self._last_token: dt.datetime = None

    @property
    def _short_security_token(self):
        def clear_token():
            tokens.remove(token)

        data: dict = self.hass.data.setdefault(DOMAIN_DATA, {})
        data = data.setdefault(MEDIA_SOURCE, {})
        tokens: List[str] = data.setdefault(SHORT_TOKENS, [])
        if len(tokens) < 1 or (
            self._last_token and (self._last_token - dt_utils.now()).seconds >= 1800
        ):
            self._last_token = dt_utils.now()
            tokens.append(secrets.token_hex())
            async_call_later(self.hass, 3600, clear_token)
        token = next(iter(tokens), None)
        return token

    async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
        """Resolve a media item to a playable item."""
        _, camera_id, event_id = async_parse_identifier(item)

        data: dict = self.hass.data[self.domain]
        entry: dict = data.get(camera_id) if camera_id else None
        base: ReolinkBase = entry.get(BASE) if entry else None
        if not base:
            raise BrowseError("Camera does not exist.")

        file = unquote_plus(event_id)
        if not file:
            raise BrowseError("Event does not exist.")
        _LOGGER.debug("file = %s", file)

        url = await base.api.get_vod_source(file)
        _LOGGER.debug("Load VOD %s", url)
        stream = create_stream(self.hass, url)
        stream.add_provider("hls", timeout=3600)
        url: str = stream.endpoint_url("hls")
        # the media browser seems to have a problem with the master_playlist
        # ( it does not load the referenced playlist ) so we will just
        # force the reference playlist instead, this seems to work
        # though technically wrong
        url = url.replace("master_", "")
        _LOGGER.debug("Proxy %s", url)
        return PlayMedia(url, MIME_TYPE)

    async def async_browse_media(
        self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES
    ) -> BrowseMediaSource:
        """Browse media."""

        try:
            source, camera_id, event_id = async_parse_identifier(item)
        except Unresolvable as err:
            raise BrowseError(str(err)) from err

        _LOGGER.debug("Browsing %s, %s, %s", source, camera_id, event_id)

        data: dict = self.hass.data[self.domain]
        entry: dict = data.get(camera_id) if camera_id else None
        base: ReolinkBase = entry.get(BASE) if entry else None
        if camera_id and not base:
            raise BrowseError("Camera does not exist.")

        if event_id and not "/" in event_id:
            raise BrowseError("Event does not exist.")

        return await self._async_browse_media(source, camera_id, event_id, base)

    async def _async_browse_media(
        self,
        source: str,
        camera_id: str = None,
        event_id: str = None,
        base: ReolinkBase = None,
    ) -> BrowseMediaSource:
        """ actual browse after input validation """

        start_date: dt.datetime = None

        def create_item(title: str, path: str, thumbnail: bool = False):
            nonlocal self, camera_id, event_id, start_date

            if not title or not path:
                if event_id and "/" in event_id:
                    year, *rest = event_id.split("/", 3)
                    month = rest[0] if len(rest) > 0 else None
                    day = rest[1] if len(rest) > 1 else None

                    start_date = dt.datetime.combine(
                        dt.date(
                            int(year),
                            int(month) if month else 1,
                            int(day) if day else 1,
                        ),
                        dt.time.min,
                        dt_utils.now().tzinfo,
                    )

                    title = f"{start_date.date()}"
                    path = f"{source}/{camera_id}/{event_id}"
                elif base:
                    title = base.name
                    path = f"{source}/{camera_id}"
                else:
                    title = self.name
                    path = source + "/"

            media_class = (
                MEDIA_CLASS_DIRECTORY
                if not event_id or "/" in event_id
                else MEDIA_CLASS_VIDEO
            )

            media = BrowseMediaSource(
                domain=self.domain,
                identifier=path,
                media_class=media_class,
                media_content_type=MEDIA_TYPE_VIDEO,
                title=title,
                can_play=not bool(media_class == MEDIA_CLASS_DIRECTORY),
                can_expand=bool(media_class == MEDIA_CLASS_DIRECTORY),
            )

            if thumbnail:
                url = THUMBNAIL_URL.format(camera_id=camera_id, event_id=event_id)
                # cannot do authsign as we are in a websocket and isloated from auth and context
                # we will continue to use custom tokens
                # request = current_request.get()
                # refresh_token_id = request.get(KEY_HASS_REFRESH_TOKEN_ID)
                # if not refresh_token_id:
                #     _LOGGER.debug("no token? %s", list(request.keys()))

                # # leave expiration 30 seconds?
                # media.thumbnail = async_sign_path(
                #     self.hass, refresh_token_id, url, dt.timedelta(seconds=30)
                # )
                media.thumbnail = f"{url}?token={self._short_security_token}"

            if not media.can_play and not media.can_expand:
                _LOGGER.debug(
                    "Camera %s with event %s without media url found",
                    camera_id,
                    event_id,
                )
                raise IncompatibleMediaSource

            return media

        def create_root_children():
            nonlocal base, camera_id

            children = []
            data: Dict[str, dict] = self.hass.data[self.domain]
            for entry_id in data:
                entry = data[entry_id]
                if not isinstance(entry, dict) or not BASE in entry:
                    continue
                base = entry[BASE]
                if not base.api.hdd_info:
                    continue
                camera_id = entry_id
                child = create_item(None, None)
                children.append(child)

            return children

        async def create_day_children():
            nonlocal event_id

            children = []
            end_date = dt_utils.now()
            start_date = dt.datetime.combine(
                end_date.date().replace(day=1), dt.time.min
            )
            if base.playback_months > 1:
                start_date -= relativedelta.relativedelta(
                    months=int(base.playback_months)
                )

            search, _ = await base.api.send_search(start_date, end_date, True)

            if not search is None:
                for status in search:
                    year = status["year"]
                    month = status["mon"]
                    for day, flag in enumerate(status["table"], start=1):
                        if flag == "1":
                            event_id = f"{year}/{month}/{day}"
                            child = create_item(None, None)
                            children.append(child)

            children.reverse()
            return children

        async def create_vod_children():
            nonlocal base, start_date, event_id

            children = []
            end_date = dt.datetime.combine(
                start_date.date(), dt.time.max, start_date.tzinfo
            )

            _, files = await base.send_search(start_date, end_date)

            for file in files:
                end_date = searchtime_to_datetime(file["EndTime"], end_date.tzinfo)
                start_date = searchtime_to_datetime(file["StartTime"], end_date.tzinfo)
                event_id = str(start_date.timestamp())
                evt_id = f"{camera_id}/{quote_plus(file['name'])}"
                # self._file_cache[evt_id] = file["name"]
                thumbnail = os.path.isfile(
                    f"{base.thumbnail_path}/{event_id}.{EXTENSION}"
                )

                time = start_date.time()
                duration = end_date - start_date
                child = create_item(
                    f"{time} {duration}", f"{source}/{evt_id}", thumbnail
                )
                children.append(child)

            children.reverse()

            return children

        if base and event_id and not "/" in event_id:
            event = base.in_memory_events[event_id]
            start_date = event.start

        media = create_item(None, None)

        if not media.can_expand:
            return media

        if not camera_id:
            media.children = create_root_children()
            return media

        if not start_date:
            media.children = await create_day_children()
        else:
            media.children = await create_vod_children()

        return media


class ReolinkSourceVODView(HomeAssistantView):
    """ VOD security handler """

    url = VOD_URL
    name = "api:" + DOMAIN + ":video"
    cors_allowed = True
    requires_auth = False

    def __init__(self, hass: HomeAssistant):
        """Initialize media view """

        self.hass = hass

    async def get(
        self, request: web.Request, camera_id: str, event_id: str
    ) -> web.Response:
        """ start a GET request. """

        authenticated = request.get(KEY_AUTHENTICATED, False)
        if not authenticated:
            token: str = request.query.get("token")
            if not token:
                raise web.HTTPUnauthorized()

            data: dict = self.hass.data.get(DOMAIN_DATA)
            data = data.get(MEDIA_SOURCE) if data else None
            tokens: List[str] = data.get(LONG_TOKENS) if data else None
            if not tokens or not token in tokens:
                raise web.HTTPUnauthorized()

        if not camera_id or not event_id:
            raise web.HTTPNotFound()

        data: Dict[str, dict] = self.hass.data[DOMAIN]
        base: ReolinkBase = (
            data[camera_id].get(BASE, None) if camera_id in data else None
        )
        if not base:
            _LOGGER.debug("camera %s not found", camera_id)
            raise web.HTTPNotFound()

        file = unquote_plus(event_id)
        url = await base.api.get_vod_source(file)
        return web.HTTPTemporaryRedirect(url)


class ReolinkSourceThumbnailView(HomeAssistantView):
    """ Thumbnial view handler """

    url = THUMBNAIL_URL
    name = "api:" + DOMAIN + ":image"
    cors_allowed = True
    requires_auth = False

    def __init__(self, hass: HomeAssistant):
        """Initialize media view """

        self.hass = hass

    async def get(
        self,
        request: web.Request,  # pylint: disable=unused-argument
        camera_id: str,
        event_id: str,
    ) -> web.Response:
        """ start a GET request. """

        authenticated = request.get(KEY_AUTHENTICATED, False)
        if not authenticated:
            token: str = request.query.get("token")
            if not token:
                raise web.HTTPUnauthorized()

            data: dict = self.hass.data.get(DOMAIN_DATA)
            data = data.get(MEDIA_SOURCE) if data else None
            tokens: List[str] = data.get(SHORT_TOKENS) if data else None
            if not tokens or not token in tokens:
                raise web.HTTPUnauthorized()

        if not camera_id or not event_id:
            raise web.HTTPNotFound()

        data: Dict[str, dict] = self.hass.data[DOMAIN]
        base: ReolinkBase = (
            data[camera_id].get(BASE, None) if camera_id in data else None
        )
        if not base:
            _LOGGER.debug("camera %s not found", camera_id)
            raise web.HTTPNotFound()

        thumbnail = f"{base.thumbnail_path}/{event_id}.{EXTENSION}"
        return web.FileResponse(thumbnail)


@callback
def async_parse_identifier(
    item: MediaSourceItem,
) -> Tuple[str, str, Optional[str]]:
    """Parse identifier."""
    if not item.identifier:
        return "events", "", None

    source, path = item.identifier.lstrip("/").split("/", 1)

    if source != "events":
        raise Unresolvable("Unknown source directory.")

    if "/" in path:
        camera_id, event_id = path.split("/", 1)

        return source, camera_id, event_id

    return source, path, None
