from __future__ import annotations

from abc import ABCMeta
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, ClassVar

from commitizen.changelog import IncrementalMergeInfo, Metadata
from commitizen.config.base_config import BaseConfig
from commitizen.git import GitTag
from commitizen.tags import TagRules, VersionTag
from commitizen.version_schemes import get_version_scheme

from . import ChangelogFormat

if TYPE_CHECKING:
    from commitizen.config.base_config import BaseConfig


class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
    """
    Base class to extend to implement a changelog file format.
    """

    extension: ClassVar[str] = ""
    alternative_extensions: ClassVar[set[str]] = set()

    def __init__(self, config: BaseConfig) -> None:
        # Constructor needs to be redefined because `Protocol` prevent instantiation by default
        # See: https://bugs.python.org/issue44807
        self.config = config
        self.tag_rules = TagRules(
            scheme=get_version_scheme(self.config.settings),
            tag_format=self.config.settings["tag_format"],
            legacy_tag_formats=self.config.settings["legacy_tag_formats"],
            ignored_tag_formats=self.config.settings["ignored_tag_formats"],
        )

    def get_metadata(self, filepath: str) -> Metadata:
        file = Path(filepath)
        if not file.is_file():
            return Metadata()

        with file.open(encoding=self.config.settings["encoding"]) as changelog_file:
            return self.get_metadata_from_file(changelog_file)

    def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
        meta = Metadata()
        unreleased_level: int | None = None
        for index, line in enumerate(file):
            line = line.strip().lower()

            unreleased: int | None = None
            if "unreleased" in line:
                unreleased = self.parse_title_level(line)
            # Try to find beginning and end lines of the unreleased block
            if unreleased:
                meta.unreleased_start = index
                unreleased_level = unreleased
                continue
            elif unreleased_level and self.parse_title_level(line) == unreleased_level:
                meta.unreleased_end = index

            # Try to find the latest release done
            parsed_version = self.parse_version_from_title(line)
            if parsed_version:
                meta.latest_version = parsed_version.version
                meta.latest_version_tag = parsed_version.tag
                meta.latest_version_position = index
                break  # there's no need for more info
        if meta.unreleased_start is not None and meta.unreleased_end is None:
            meta.unreleased_end = index

        return meta

    def get_latest_full_release(self, filepath: str) -> IncrementalMergeInfo:
        file = Path(filepath)
        if not file.is_file():
            return IncrementalMergeInfo()

        with file.open(encoding=self.config.settings["encoding"]) as changelog_file:
            return self.get_latest_full_release_from_file(changelog_file)

    def get_latest_full_release_from_file(self, file: IO[Any]) -> IncrementalMergeInfo:
        latest_version_index: int | None = None
        for index, line in enumerate(file):
            latest_version_index = index
            line = line.strip().lower()

            parsed_version = self.parse_version_from_title(line)
            if (
                parsed_version
                and not self.tag_rules.extract_version(
                    GitTag(parsed_version.tag, "", "")
                ).is_prerelease
            ):
                return IncrementalMergeInfo(name=parsed_version.tag, index=index)
        return IncrementalMergeInfo(index=latest_version_index)

    def parse_version_from_title(self, line: str) -> VersionTag | None:
        """
        Extract the version from a title line if any
        """
        raise NotImplementedError(
            "Default `get_metadata_from_file` requires `parse_version_from_changelog` to be implemented"
        )

    def parse_title_level(self, line: str) -> int | None:
        """
        Get the title level/type of a line if any
        """
        raise NotImplementedError(
            "Default `get_metadata_from_file` requires `parse_title_type_of_line` to be implemented"
        )
