import dataclasses
import re
from typing import Sequence, Optional, FrozenSet, Tuple, Dict, List

from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable
from debputy.substitution import Substitution
from debputy.util import _error

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


class AcceptableMigrationIssues:
    def __init__(self, values: FrozenSet[str]):
        self._values = values

    def __contains__(self, item: str) -> bool:
        return item in self._values or "ALL" in self._values


class UnsupportedFeature(RuntimeError):
    @property
    def message(self) -> str:
        return self.args[0]

    @property
    def issue_keys(self) -> Optional[Sequence[str]]:
        if len(self.args) < 2:
            return None
        return self.args[1]


class ConflictingChange(RuntimeError):
    @property
    def message(self) -> str:
        return self.args[0]


@dataclasses.dataclass
class FeatureMigration:
    tagline: str
    successful_manifest_changes: int
    already_present: int
    warnings: Sequence[str] = tuple()
    remove_paths_on_success: Sequence[str] = tuple()
    rename_paths_on_success: Sequence[Tuple[str, str]] = tuple()
    assumed_compat: Optional[int] = None

    @property
    def anything_to_do(self) -> bool:
        return bool(self.total_changes_involved)

    @property
    def performed_changes(self) -> int:
        return (
            self.successful_manifest_changes
            + len(self.remove_paths_on_success)
            + len(self.rename_paths_on_success)
        )

    @property
    def total_changes_involved(self) -> int:
        return (
            self.successful_manifest_changes
            + len(self.warnings)
            + len(self.remove_paths_on_success)
            + len(self.rename_paths_on_success)
        )


class DHMigrationSubstitution(Substitution):
    def __init__(
        self,
        dpkg_arch_table: DpkgArchitectureBuildProcessValuesTable,
        acceptable_migration_issues: AcceptableMigrationIssues,
        warnings: List[str],
    ) -> None:
        self._acceptable_migration_issues = acceptable_migration_issues
        self._dpkg_arch_table = dpkg_arch_table
        self._warnings = warnings
        self._builtin_substs = {
            "Tab": "\t",
            "Space": " ",
            "Newline": "{{NEWLINE}}",
            "Dollar": "${}",
        }

    def _replacement(self, key: str, definition_source: str) -> str:
        if key in self._builtin_substs:
            return self._builtin_substs[key]
        if key in self._dpkg_arch_table:
            return "{{" + key + "}}"
        if key.startswith("env:"):
            if "dh-subst-env" not in self._acceptable_migration_issues:
                _error(
                    f'The environment does not contain the variable "{key}" '
                    f"(error occurred while trying to process {definition_source})"
                )
                raise UnsupportedFeature(
                    "Use of environment based substitution variable {{"
                    + key
                    + "}} is not"
                    f" supported in debputy. The variable was spotted at {definition_source}",
                    ["dh-subst-env"],
                )
        elif "dh-subst-unknown-variable" not in self._acceptable_migration_issues:
            raise UnsupportedFeature(
                "Unknown substitution variable {{"
                + key
                + "}}, which does not have a known"
                f" counter part in debputy. The variable was spotted at {definition_source}",
                ["dh-subst-unknown-variable"],
            )
        self._warnings.append(
            "TODO: MANUAL MIGRATION of unresolved substitution variable {{"
            + key
            + "}} from"
            f" {definition_source}"
        )
        return "{{" + key + "}}"

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

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