import contextlib
from typing import Iterator, Optional, Mapping, NoReturn, Union, Any

from debputy.highlevel_manifest import PackageTransformationDefinition
from debputy.manifest_conditions import ManifestCondition
from debputy.manifest_parser.exceptions import ManifestParseException
from debputy.manifest_parser.util import AttributePath
from debputy.packages import BinaryPackage
from debputy.substitution import Substitution


class ParserContextData:
    @property
    def binary_packages(self) -> Mapping[str, BinaryPackage]:
        raise NotImplementedError

    @property
    def _package_states(self) -> Mapping[str, PackageTransformationDefinition]:
        raise NotImplementedError

    @property
    def is_single_binary_package(self) -> bool:
        return len(self.binary_packages) == 1

    def single_binary_package(self) -> Optional[BinaryPackage]:
        if len(self.binary_packages) != 1:
            return None
        return next(iter(self.binary_packages.values()))

    def _error(self, msg: str) -> "NoReturn":
        raise ManifestParseException(msg)

    def is_known_package(self, package_name: str) -> bool:
        return package_name in self._package_states

    def binary_package_data(self, package_name: str) -> PackageTransformationDefinition:
        if package_name not in self._package_states:
            self._error(
                f'The package "{package_name}" is not present in the debian/control file (could not find'
                f' "Package: {package_name}" in a binary paragraph) nor is it a -dbgsym package for one'
                " for a package in debian/control."
            )
        return self._package_states[package_name]

    @contextlib.contextmanager
    def binary_package_context(
        self,
        package_name: str,
    ) -> Iterator[PackageTransformationDefinition]:
        package_state = self.binary_package_data(package_name)
        self._substitution_stack.append(package_state.substitution)
        self._package_state_stack.append(package_state)
        ss_len = len(self._substitution_stack)
        ps_len = len(self._package_state_stack)
        yield package_state
        if ss_len != len(self._substitution_stack) or ps_len != len(
            self._package_state_stack
        ):
            raise RuntimeError("Internal error: Unbalanced stack manipulation detected")
        self._substitution_stack.pop()
        self._package_state_stack.pop()

    @property
    def substitution(self) -> Substitution:
        return self._substitution_stack[-1]

    @property
    def current_binary_package_state(self) -> PackageTransformationDefinition:
        if not self._package_state_stack:
            raise RuntimeError("Invalid state: Not in a binary package context")
        return self._package_state_stack[-1]

    def parse_manifest_condition(
        self,
        condition: Union[str, Mapping[str, Any]],
        path: AttributePath,
    ) -> ManifestCondition:
        raise NotImplementedError
