"""
Artifact provider for discovering RPMs from repository files.
"""

from re import Pattern
from typing import Optional
from urllib.parse import urlparse

import tmt.log
import tmt.utils
from tmt.container import container, simple_field
from tmt.guest import DownloadError, Guest
from tmt.package_managers import YUM_REPOS_DIR
from tmt.steps import DefaultNameGenerator
from tmt.steps.prepare.artifact.providers import (
    ArtifactInfo,
    ArtifactProvider,
    ArtifactProviderId,
    Repository,
    provides_artifact_provider,
)
from tmt.utils import Path, PrepareError, RunError

# Counter for generating unique repository names in the format ``tmt-repo-default-{n}``.
_REPO_NAME_GENERATOR = DefaultNameGenerator(known_names=[])


@provides_artifact_provider('repository-file')
@container
class RepositoryFileProvider(ArtifactProvider):
    """
    Provider for making RPM artifacts from a repository discoverable without downloading them.

    The provider identifier should start with 'repository-file:' followed by a URL to a .repo file,
    e.g., "repository-file:https://download.docker.com/linux/centos/docker-ce.repo".

    The provider downloads the .repo file to the guest's ``/etc/yum.repos.d/`` directory,
    and lists RPMs available in the defined repositories without downloading them, acting as a
    discovery-only provider. Artifacts are all available RPM packages listed in the repository.

    :param raw_id: The full provider identifier, starting with 'repository-file:'.
    :param logger: Logger instance for outputting messages.
    :raises GeneralError: If the .repo file URL is invalid.
    """

    repository: Repository = simple_field(init=False)

    @classmethod
    def _extract_provider_id(cls, raw_id: str) -> ArtifactProviderId:
        prefix = 'repository-file:'
        if not raw_id.startswith(prefix):
            raise ValueError(f"Invalid repository provider format: '{raw_id}'.")
        value = raw_id[len(prefix) :]
        if not value:
            raise ValueError("Missing repository URL.")
        return value

    def _download_artifact(self, artifact: ArtifactInfo, guest: Guest, destination: Path) -> None:
        """This provider only discovers repos; it does not download individual RPMs."""
        raise AssertionError(
            "RepositoryFileProvider does not support downloading individual RPMs."
        )

    def fetch_contents(
        self,
        guest: Guest,
        download_path: tmt.utils.Path,
        exclude_patterns: Optional[list[Pattern[str]]] = None,
    ) -> list[tmt.utils.Path]:
        # Fetches and initializes the repository from the URL.
        # Repository provider does not download individual artifacts. Instead, it fetches
        # the repository file which will be installed via get_repositories(). Packages are
        # then available through the package manager.
        # It returns an Empty list, as no individual artifact files are downloaded.

        self.logger.info(f"Initializing repository provider with URL: {self.id}")

        parsed = urlparse(self.id)
        parsed_path = Path(parsed.path)
        if parsed.scheme == 'file':
            # Read .repo file from the local (controller) filesystem.
            self.logger.info(f"Reading repository file from local path: {parsed_path}")
            self.logger.debug(f"Absolute path of the repository file: '{parsed_path.resolve()}'")
            self.repository = Repository.from_file_path(file_path=parsed_path, logger=self.logger)
        else:
            repo_filename = parsed_path.name
            remote_path = YUM_REPOS_DIR / repo_filename
            try:
                guest.download(self.id, remote_path)
            except DownloadError as error:
                raise PrepareError(
                    f"Failed to download repository file '{self.id}' to the guest."
                ) from error
            try:
                output = guest.execute(tmt.utils.Command("cat", remote_path))
            except tmt.utils.RunError as error:
                raise PrepareError(f"Failed to read '{repo_filename}' from the guest.") from error
            self.repository = Repository.from_content(
                output.stdout or '', repo_filename.removesuffix('.repo'), self.logger
            )

        self.logger.info(
            f"Repository initialized: {self.repository.name} "
            f"(repo IDs: {', '.join(self.repository.repo_ids)})"
        )
        return []

    def get_repositories(self) -> list[Repository]:
        self.logger.info(f"Providing repository '{self.repository.name}' for installation ")
        return [self.repository]


def create_repository(
    artifact_dir: Path,
    guest: Guest,
    logger: tmt.log.Logger,
    priority: int,
    repo_name: Optional[str] = None,
) -> Repository:
    """
    Create a local RPM repository from a directory on the guest.

    Creates repository metadata and prepares a Repository object. Does not install
    the repository on the guest system. Use install_repository() to make it visible
    to the package manager.

    :param artifact_dir: Path to the directory on the guest containing RPM files.
    :param guest: Guest instance where the repository metadata will be created.
    :param logger: Logger instance for outputting debug and error messages.
    :param repo_name: Name for the repository. If not provided, generates a unique
        name using the format ``tmt-repo-default-{n}``.
    :param priority: Repository priority. Lower values have higher priority.
    :returns: Repository object representing the newly created repository.
    :raises PrepareError: If the package manager does not support creating repositories
        or if metadata creation fails.
    """
    repo_name = repo_name or f"tmt-repo-{_REPO_NAME_GENERATOR.get()}"

    logger.info(f"Creating repository '{repo_name}' from directory '{artifact_dir}'")

    # Ensure the artifact directory exists
    guest.execute(
        tmt.utils.Command('mkdir', '-p', artifact_dir),
        silent=True,
    )

    # Create Repository Metadata
    logger.info(f"Creating repository metadata for '{artifact_dir}'.")
    try:
        guest.package_manager.create_repository(artifact_dir)
    except RunError as error:
        raise PrepareError(f"Failed to create repository metadata in '{artifact_dir}'") from error

    # Generate .repo File Content
    repo_string = f"""[{tmt.utils.sanitize_name(repo_name)}]
name={repo_name}
baseurl=file://{artifact_dir}
enabled=1
gpgcheck=0
priority={priority}"""

    logger.debug(f"Generated .repo file content:\n{repo_string}")

    # Create Repository Object
    created_repository = Repository.from_content(
        content=repo_string, name=repo_name, logger=logger
    )

    logger.info(f"Successfully created repository '{created_repository.name}' ")

    return created_repository
