from __future__ import annotations

import re
from pathlib import Path
from typing import Optional, Dict

import aiohttp

from clarabot_nano.indexer.config import IndexerConfig


ALL_SPACE_SEARCH_RE = re.compile(r"\s+")
SPACE_SEARCH_RE = re.compile(r"[^\S\n\x0b\x0c\r\x85\u2028\u2029]+")
NEWLINE_SEARCH_RE = re.compile(r"[\n\x0b\x0c\r\x85\u2028\u2029]+")
NEWLINE_STRIP_RE = re.compile(r"\s*(?:[\n\x0b\x0c\r\x85\u2028\u2029]+\s*)+")
# STARTS_WITH_BOUNDARY_RE = re.compile(r"^.\b")
# ENDS_WITH_BOUNDARY_RE = re.compile(r"\b.$")

# NOTE: big, strike and font are not HTML5 compatible
INLINE_TEXT_TAGS = {"a", "b", "i", "u", "s", "em", "strong", "small", "img", "big", "strike", "font"}
HEADER_TEXT_TAGS = {"h1", "h2", "h3", "h4"}


class ExtractError(Exception):
    def __init__(self, extraction: Optional[AbstractExtraction] = None):
        Exception.__init__(self, extraction)

    @property
    def extraction(self) -> Optional[AbstractExtraction]:
        return self.args[0]


class ExtractorInoperable(ExtractError):
    """
    Raised by the Extractor.ensure_operable() and Extractor.extract() methods if the extractor does not work at all.
    """


class RetryExtractError(ExtractError):
    """
    Raised by the Extractor.extract() method if retrying could succeed from an error.
    If no more retry attempts will be made the caller may use the extraction from this instance... maybe there's
    something usable on it.
    """

    def __init__(self, extraction: AbstractExtraction):
        ExtractError.__init__(self, extraction)


class ServerInternalExtractError(RetryExtractError):
    pass


class IgnoredMimeType(ExtractError):
    pass


class EnoughText(ExtractError):
    pass


class AbstractCollector(object):
    def start(self, tag: str, attrs: dict):
        raise NotImplementedError()

    def end(self, tag: str):
        raise NotImplementedError()

    def data(self, text: str):
        raise NotImplementedError()

    def comment(self, text: str):
        raise NotImplementedError()

    def close(self):
        raise NotImplementedError()


class CollectorHandler(AbstractCollector):
    TAG: str = None

    def __init__(self, sub_handlers: Dict[str, CollectorHandler] = None):
        self.sub_handlers = sub_handlers or {}

    def init(self, attrs: dict):
        pass

    def start(self, tag: str, attrs: dict):
        pass

    def end(self, tag: str):
        pass

    def data(self, text: str):
        pass

    def comment(self, text: str):
        pass

    def close(self):
        pass


class AbstractExtraction(object):
    def get_summary(self) -> str:
        raise NotImplementedError()

    def get_content(self) -> Optional[str]:
        raise NotImplementedError()


class AbstractContentExtractor(object):
    def __init__(self, session: aiohttp.ClientSession, ec: IndexerConfig):
        self.session = session
        self.ec = ec

    async def extract(self, path: Path) -> AbstractExtraction:
        """
        Extract the contents from a file. Error handling of the extractor should be done here.
        """
        raise NotImplementedError()

    async def ensure_operable(self, timeout: float = 30.0):
        """
        Check if extractor is operable, raise fatal error if it won't work.
        """
        raise NotImplementedError()

    async def check_operable(self, timeout: float = 5.0) -> bool:
        """
        Can be used to check if the extractor seems to be working.
        """
        raise NotImplementedError()
