from dataclasses import dataclass
from enum import auto, StrEnum
from typing import Any

# import PyO3 bindings
from . import _rust


# keep in sync with the Rust enum
class Severity(StrEnum):
    """Severity associated with lints or other warnings."""

    Fatal = auto()
    Error = auto()
    Warning = auto()
    Info = auto()
    Debug = auto()


# keep in sync with the Rust enum
class ReportItemKind(StrEnum):
    """String representation / ID of the report item kind."""

    MissingRepositoryUrl = "MissingRepositoryUrl"
    InvalidRepoUrl = "InvalidRepoUrl"
    InvalidGitRef = "InvalidGitRef"
    MissingVcsInfo = "MissingVcsInfo"
    NoPathInVcsInfo = "NoPathInVcsInfo"
    NotFoundInRepo = "NotFoundInRepo"
    DirtyRepository = "DirtyRepository"
    MetadataMismatch = "MetadataMismatch"
    BrokenSymlinkInCrate = "BrokenSymlinkInCrate"
    BrokenSymlinkInRepo = "BrokenSymlinkInRepo"
    InvalidSymlinkInCrate = "InvalidSymlinkInCrate"
    InvalidSymlinkInRepo = "InvalidSymlinkInRepo"
    MissingFile = "MissingFile"
    ContentMismatch = "ContentMismatch"
    LineEndings = "LineEndings"
    Permissions = "Permissions"


# keep in sync with the Rust enum
class DiffItemKind(StrEnum):
    """String representation / ID of the diff item kind."""

    FileAdded = "FileAdded"
    FileRemoved = "FileRemoved"
    FileChanged = "FileChanged"
    LineEndingsChange = "LineEndingsChange"
    PermissionChange = "PermissionChange"
    NameChange = "NameChange"
    VersionChange = "VersionChange"
    EditionChange = "EditionChange"
    RustVersionChange = "RustVersionChange"
    AuthorsChange = "AuthorsChange"
    DescriptionChange = "DescriptionChange"
    LicenseChange = "LicenseChange"
    LicenseFileChange = "LicenseFileChange"
    ReadmeChange = "ReadmeChange"
    CategoriesChange = "CategoriesChange"
    KeywordsChange = "KeywordsChange"
    RepositoryChange = "RepositoryChange"
    HomepageChange = "HomepageChange"
    DocumentationChange = "DocumentationChange"
    LinksChange = "LinksChange"
    DefaultRunChange = "DefaultRunChange"
    MetadataChange = "MetadataChange"
    DependencyAdded = "DependencyAdded"
    DependencyRemoved = "DependencyRemoved"
    DependencyUpgraded = "DependencyUpgraded"
    DependencyDowngraded = "DependencyDowngraded"
    DependencyFeatures = "DependencyFeatures"
    DependencyOptionality = "DependencyOptionality"
    TargetAdded = "TargetAdded"
    TargetRemoved = "TargetRemoved"
    TargetChanged = "TargetChanged"
    FeatureAdded = "FeatureAdded"
    FeatureRemoved = "FeatureRemoved"
    FeatureChanged = "FeatureChanged"


@dataclass(frozen=True)
class ReportItem:
    kind: ReportItemKind
    severity: Severity
    message: str
    extra: str | None
    data: dict[str, Any]

    def __repr__(self) -> str:
        return f"{self.severity}: {self.kind} ({self.data})"

    def __str__(self) -> str:
        return f"{self.severity}: {self.message}"


@dataclass(frozen=True)
class Report:
    items: list[ReportItem]

    def __repr__(self) -> str:
        return repr(self.items)

    def __str__(self) -> str:
        return "\n".join(str(item) for item in self.items)


@dataclass(frozen=True)
class DiffItem:
    kind: DiffItemKind
    severity: Severity
    message: str
    extra: str | None
    data: dict[str, Any]

    def __repr__(self) -> str:
        return f"{self.severity}: {self.kind} ({self.data})"

    def __str__(self) -> str:
        return f"{self.severity}: {self.message}"


@dataclass(frozen=True)
class Diff:
    items: list[DiffItem]

    def __repr__(self) -> str:
        return repr(self.items)

    def __str__(self) -> str:
        return "\n".join(str(item) for item in self.items)


class Crate:
    def __init__(self, inner: _rust.RustCrate) -> None:
        self._inner = inner

    def __repr__(self) -> str:
        return f'Crate(name = "{self.name}", version = "{self.version}")'

    @staticmethod
    def download(name: str, version: str) -> "Crate":
        """Download crate sources from crates.io."""
        return Crate(_rust.RustCrate.download(name, version))

    @staticmethod
    def local(name: str, version: str, path: str) -> "Crate":
        """Load crate sources from local ".crate" file."""
        return Crate(_rust.RustCrate.local(name, version, path))

    @property
    def name(self) -> str:
        return self._inner.name()

    @property
    def version(self) -> str:
        return self._inner.version()

    def report(
        self,
        /,
        repository: str | None = None,
        path_in_vcs: str | None = None,
        vcs_ref: str | None = None,
    ) -> Report:
        """Compare crate against the contents of the project's version control system."""
        options = _rust.RustReportOptions.from_options(repository, path_in_vcs, vcs_ref)
        if options.is_empty():
            report = self._inner.report()
        else:
            report = self._inner.report(options)

        return Report(
            [
                ReportItem(item.kind(), item.severity(), item.message(), item.extra(), item.data())
                for item in report.items()
            ]
        )

    def diff(self, other: "Crate") -> Diff:
        """Compare crate against a different version of the same crate."""
        diff = self._inner.diff(other._inner)
        return Diff(
            [
                DiffItem(item.kind(), item.severity(), item.message(), item.extra(), item.data())
                for item in diff.items()
            ]
        )
