import re
from dataclasses import dataclass
from enum import Enum

from components.parser.abbreviations.constants import (
    ABBREVIATION_CLEANUP_REPLACEMENTS,
    BLACKLIST,
    DASH_PATTERN,
    MAX_LENGTH,
    PREFIX_PARTS_TO_REMOVE,
    REMOVING_SUBSTRINGS,
)
from components.parser.abbreviations.porter import Porter


class AbbreviationType(str, Enum):
    ABBREVIATION = 'abbreviation'
    SHORTENING = 'shortening'
    UNKNOWN = 'unknown'


@dataclass
class Abbreviation:
    short_form: str
    full_form: str
    abbreviation_type: AbbreviationType = AbbreviationType.UNKNOWN

    _processed: bool = False
    document_id: int | None = None

    def process(self) -> 'Abbreviation':
        """
        Производит пост-обработку сокращения и полной формы.
        - Определяет тип сокращения.
        - Удаляет префикс из короткой формы и мусор из полной формы.
        - В зависимости от типа сокращения адаптирует его под нужный вид.
        """
        if self._processed:
            return

        self._define_abbreviation_type()

        self.short_form = self._remove_prefix(self.short_form)
        self.full_form = self._remove_trash(self.full_form)

        if self._abbreviation_type == AbbreviationType.SHORTENING:
            self._process_shortening()
        elif self._abbreviation_type == AbbreviationType.ABBREVIATION:
            self._process_abbreviation()

        self._processed = True

        return self

    def apply(self, text: str) -> str:
        """
        Применяет аббревиатуру к тексту.

        Args:
            text (str): Текст для обработки.

        Returns:
            str: Обработанный текст.
        """
        if self._abbreviation_type == AbbreviationType.UNKNOWN:
            return text

        if self._abbreviation_type == AbbreviationType.SHORTENING:
            return self._apply_shortening(text)

        elif self._abbreviation_type == AbbreviationType.ABBREVIATION:
            return self._apply_abbreviation(text)

    def _apply_shortening(self, text: str) -> str:
        """
        Применяет сокращение к тексту.

        Args:
            text (str): Текст для обработки.

        Returns:
            str: Обработанный текст.
        """
        matches = list(re.finditer(self.short_form, text))
        for i in range(len(matches) - 1, 1, -1):
            m = matches[i]
            pos1 = m.start()
            m2 = re.match(r'[A-Za-zА-Яа-я]+', text[pos1:])
            pos2 = pos1 + m2.end()
            explanation = self.full_form
            m3 = re.match(r'[A-Za-zА-Яа-я]+', explanation)
            explanation = explanation[m3.end() :]
            text = text[:pos2] + explanation + text[pos2:]
        return text

    def _apply_abbreviation(self, text: str) -> str:
        """
        Применяет аббревиатуру к тексту.

        Args:
            text (str): Текст для обработки.

        Returns:
            str: Обработанный текст.
        """
        matches = list(re.finditer(self.short_form, text))
        for i in range(len(matches) - 1, 0, -1):
            m = matches[i]
            text = f'{text[: m.start()]}{self.short_form} ({self.full_form}){text[m.end():]}'
        return text

    def _define_abbreviation_type(self) -> None:
        """
        Определяет тип сокращения.
        """
        if self._check_abbreviation(self.full_form):
            self._abbreviation_type = AbbreviationType.ABBREVIATION
        elif self._check_shortening(self.full_form):
            self._abbreviation_type = AbbreviationType.SHORTENING
        else:
            self._abbreviation_type = AbbreviationType.UNKNOWN

    def _process_shortening(self) -> None:
        """
        Обрабатывает сокращение.
        """
        key = Porter.stem(self.short_form)
        pos = self.full_form.lower().rfind(key.lower())
        if pos != -1:
            self.full_form = self.full_form[pos:]
            self.short_form = key
        else:
            self.abbreviation_type = AbbreviationType.UNKNOWN

    def _process_abbreviation(self) -> None:
        """
        Обрабатывает аббревиатуру.
        """
        uppercase_letters = re.sub('[a-zа-я, ]', '', self.short_form)
        processed_full_form = self._remove_trash_when_abbreviation(self.full_form)
        words = processed_full_form.split()
        uppercase_letters = uppercase_letters[::-1]
        words = words[::-1]

        if (len(words) <= len(uppercase_letters)) or ('ОКС НН' not in self.short_form):
            self.abbreviation_type = AbbreviationType.UNKNOWN
            return

        match = self._check_abbreviation_matches_words(uppercase_letters, words)
        if match:
            self._process_matched_abbreviation(uppercase_letters, words)
        else:
            self._process_mismatched_abbreviation()

    def _process_matched_abbreviation(
        self,
        uppercase_letters: str,
        words: list[str],
    ) -> None:
        """
        Обрабатывает аббревиатуру, которая совпадает с первыми буквами полной формы.

        Args:
            uppercase_letters (str): Заглавные буквы из сокращения.
            words (list[str]): Список слов, которые составляют аббревиатуру.
        """
        pos = len(self.full_form)
        for i in range(len(uppercase_letters)):
            pos = self.full_form.rfind(words[i], 0, pos)

        if pos != -1:
            self.full_form = self.full_form[pos:]

        else:
            self.abbreviation_type = AbbreviationType.UNKNOWN

    def _process_mismatched_abbreviation(self) -> None:
        """
        Обрабатывает аббревиатуру, которая не совпадает с первыми буквами полной формы.
        """
        first_letter = self.short_form[0]
        pos = self.full_form.rfind(first_letter)
        if pos != -1:
            self.full_form = self.full_form[pos:]
            first_letter = self.full_form[0]
            second_letter = self.full_form[1]

            if (
                ('A' < first_letter < 'Z' or 'А' < first_letter < 'Я')
                and ('a' < second_letter < 'z' or 'а' < second_letter < 'я')
                and len(self.full_form) < MAX_LENGTH
                and len(self.full_form) > len(self.short_form)
                and self.full_form not in BLACKLIST
                and '_' not in self.full_form
            ):
                return

        self.abbreviation_type = AbbreviationType.UNKNOWN

    def _check_abbreviation_matches_words(
        self,
        uppercase_letters: str,
        words: list[str],
    ) -> bool:
        """
        Проверяет, соответствует ли короткая форма аббревиатуре.

        Args:
            uppercase_letters (str): Заглавные буквы из сокращения.
            words (list[str]): Список слов, которые составляют аббревиатуру.

        Returns:
            bool: True, если аббревиатура соответствует, False в противном случае.
        """
        for j in range(len(uppercase_letters)):
            c1 = uppercase_letters[j].lower()
            c2 = words[j][0].lower()
            if c1 != c2:
                return False

        return True

    @classmethod
    def _check_abbreviation(cls, full_form: str) -> bool:
        """
        Проверяет, является ли строка аббревиатурой.

        Args:
            full_form (str): Строка для проверки.

        Returns:
            bool: True, если строка является аббревиатурой, False в противном случае.
        """
        s = cls._remove_prefix(full_form)
        words = s.split()

        for word in words:
            n = cls._count_uppercase_letters(word)
            if (n <= 1) and (word != 'и'):
                return False

        return True

    @classmethod
    def _check_shortening(cls, full_form: str) -> bool:
        """
        Проверяет, является ли строка сокращением.

        Args:
            full_form (str): Строка для проверки.

        Returns:
            bool: True, если строка является сокращением, False в противном случае.
        """
        s = cls._remove_prefix(full_form)
        words = s.split()

        if len(words) != 1:
            return False

        word = words[0]
        if word[0].isupper() and word[1:].islower() and ('Компания' not in word):
            return True

        return False

    @staticmethod
    def _remove_prefix(s: str) -> str:
        """
        Удаляет из строки префиксы типа "далее - " и "далее – ".

        Args:
            s (str): Строка для обработки.

        Returns:
            str: Обработанная строка.
        """
        for prefix_part in PREFIX_PARTS_TO_REMOVE:
            s = s.replace(prefix_part, '')
        return s.strip()

    @staticmethod
    def _remove_trash(s: str) -> str:
        """
        Удаляет из строки такие подстроки, как "ПАО", "ОАО", "№", "(".

        Args:
            s (str): Строка для обработки.

        Returns:
            str: Обработанная строка.
        """
        for substring in REMOVING_SUBSTRINGS:
            pos = s.find(substring)
            if pos != -1:
                s = s[:pos]
        return s

    @staticmethod
    def _remove_trash_when_abbreviation(s: str) -> str:
        """
        Удаляет из строки такие подстроки, как " и ", " или ", ", ", " ГО".
        Заменяет дефисы и тире на пробел.
        Это необходимо для того, чтобы правильно сопоставить аббревиатуру с полной формой.

        Args:
            s (str): Строка для обработки.

        Returns:
            str: Обработанная строка.
        """
        for old, new in ABBREVIATION_CLEANUP_REPLACEMENTS.items():
            s = s.replace(old, new)
        s = re.sub(DASH_PATTERN, ' ', s)
        return s

    @staticmethod
    def _count_uppercase_letters(s: str) -> int:
        """
        Считает количество заглавных букв в строке.

        Args:
            s (str): Строка для обработки.

        Returns:
            int: Количество заглавных букв.
        """
        return len(re.findall(r'[A-Z,А-Я]', s))