import json
import logging
from contextlib import suppress
from typing import List, Optional

import attr

from ..flv import operators as flv_ops
from ..path import extra_metadata_path
from ..utils.mixins import SwitchableMixin

__all__ = ('MetadataDumper',)

logger = logging.getLogger(__name__)


class MetadataDumper(SwitchableMixin):
    def __init__(
        self,
        dumper: flv_ops.Dumper,
        analyser: flv_ops.Analyser,
        joinpoint_extractor: flv_ops.JoinPointExtractor,
    ) -> None:
        super().__init__()

        self._dumper = dumper
        self._analyser = analyser
        self._joinpoint_extractor = joinpoint_extractor

        self._last_metadata: Optional[flv_ops.MetaData] = None
        self._last_join_points: Optional[List[flv_ops.JoinPoint]] = None

    def _do_enable(self) -> None:
        self._metadata_subscription = self._analyser.metadatas.subscribe(
            on_next=self._update_metadata, on_error=self._reset_metadata
        )
        self._join_points_subscription = (
            self._joinpoint_extractor.join_points.subscribe(self._update_join_points)
        )
        self._file_closed_subscription = self._dumper.file_closed.subscribe(
            self._dump_metadata
        )
        logger.debug('Enabled metadata dumper')

    def _do_disable(self) -> None:
        with suppress(Exception):
            self._metadata_subscription.dispose()
        with suppress(Exception):
            self._join_points_subscription.dispose()
        with suppress(Exception):
            self._file_closed_subscription.dispose()
        logger.debug('Disabled metadata dumper')

    def _update_metadata(self, metadata: flv_ops.MetaData) -> None:
        self._last_metadata = metadata

    def _reset_metadata(self, exc: Exception) -> None:
        self._last_metadata = None

    def _update_join_points(self, join_points: List[flv_ops.JoinPoint]) -> None:
        self._last_join_points = join_points

    def _dump_metadata(self, video_path: str) -> None:
        path = extra_metadata_path(video_path)
        logger.debug(f"Dumping metadata to file: '{path}'")

        assert self._last_metadata is not None
        assert self._last_join_points is not None

        data = attr.asdict(self._last_metadata, filter=lambda a, v: v is not None)
        data['joinpoints'] = list(
            map(lambda p: p.to_metadata_value(), self._last_join_points)
        )

        try:
            with open(path, 'wt', encoding='utf8') as file:
                json.dump(data, file)
        except Exception as e:
            logger.error(f'Failed to dump metadata: {e}')
        else:
            logger.debug(f"Successfully dumped metadata to file: '{path}'")
