import os
import re

from debputy.architecture_support import dpkg_architecture_table
from debputy.util import _error, glob_escape

_VAR_RE = re.compile(r"([{][{][ ]*)([A-Za-z0-9][-_:0-9A-Za-z]*)([ ]*[}][}])")


class Substitution:
    def substitute(
        self,
        value: str,
        definition_source: str,
        /,
        escape_glob_characters: bool = False,
    ) -> str:
        raise NotImplementedError

    def with_extra_substitutions(self, **extra_substitutions) -> "Substitution":
        raise NotImplementedError

    def _replacement(self, matched_key: str, definition_source: str) -> str:
        _error(
            "Cannot resolve {{" + matched_key + "}}."
            f" The error occurred while trying to process {definition_source}"
        )

    def _apply_substitution(
        self,
        pattern: re.Pattern,
        value: str,
        definition_source: str,
        /,
        escape_glob_characters: bool = False,
    ) -> str:
        replacement = value
        offset = 0
        for match in pattern.finditer(value):
            prefix, matched_key, suffix = match.groups()
            replacement_value = self._replacement(matched_key, definition_source)
            if escape_glob_characters:
                replacement_value = glob_escape(replacement_value)
            s, e = match.span()
            s += offset
            e += offset
            replacement = replacement[:s] + replacement_value + replacement[e:]
            token_fluff_len = len(prefix) + len(suffix)
            offset += len(replacement_value) - len(matched_key) - token_fluff_len
        return replacement


class NullSubstitution(Substitution):
    def substitute(
        self,
        value: str,
        definition_source: str,
        /,
        escape_glob_characters: bool = False,
    ) -> str:
        return value

    def with_extra_substitutions(self, **extra_substitutions) -> "Substitution":
        return self


NULL_SUBSTITUTION = NullSubstitution()
del NullSubstitution


class SubstitutionImpl(Substitution):
    def __init__(
        self,
        /,
        extra_substitutions=None,
        dpkg_arch_table=None,
        environment=None,
    ) -> None:
        self._extra_substitutions = (
            extra_substitutions if extra_substitutions is not None else {}
        )
        self._dpkg_arch_table = (
            dpkg_arch_table
            if dpkg_arch_table is not None
            else dpkg_architecture_table()
        )
        self._env = environment if environment is not None else os.environ
        self._builtin_substs = {
            "NL": "\n",
            "NEWLINE": "\n",
            "TAB": "\t",
            "OPEN_CURLY_BRACE": "{",
            "CLOSE_CURLY_BRACE": "}",
            "DOUBLE_OPEN_CURLY_BRACE": "{{",
            "DOUBLE_CLOSE_CURLY_BRACE": "}}",
        }

    def _replacement(self, key: str, definition_source: str) -> str:
        if key in self._builtin_substs:
            return self._builtin_substs[key]
        if key.startswith("DEB_") and key in self._dpkg_arch_table:
            return self._dpkg_arch_table[key]
        if key.startswith("env:"):
            k = key[4:]
            if k in self._env:
                return self._env[k]
            _error(
                f'The environment does not contain the variable "{key}" '
                f"(error occurred while trying to process {definition_source})"
            )
        if key in self._extra_substitutions:
            return self._extra_substitutions[key]
        _error(
            "Cannot resolve {{" + key + "}}."
            f" The error occurred while trying to process {definition_source}"
        )

    def with_extra_substitutions(self, **extra_substitutions) -> "Substitution":
        if not extra_substitutions:
            return self
        combined_extras = self._extra_substitutions.copy()
        combined_extras.update(extra_substitutions)
        return SubstitutionImpl(
            dpkg_arch_table=self._dpkg_arch_table,
            environment=self._env,
            extra_substitutions=combined_extras,
        )

    def substitute(
        self,
        value: str,
        definition_source: str,
        /,
        escape_glob_characters: bool = False,
    ) -> str:
        if "{{" not in value:
            return value
        return self._apply_substitution(
            _VAR_RE,
            value,
            definition_source,
            escape_glob_characters=escape_glob_characters,
        )
