"""
Tests für Versions-Kompatibilität zwischen Umbrella-Paket und Sub-Paketen.

Testet, dass:
1. Sub-Paket-Versionen mit den Anforderungen übereinstimmen
2. Breaking Changes erkannt werden
3. Version-Constraints aus pyproject.toml eingehalten werden
"""

import re
from importlib.metadata import version
from typing import NamedTuple

import pytest


class VersionConstraint(NamedTuple):
    """Repräsentiert einen Versions-Constraint."""

    min_version: str
    max_version: str | None
    package_name: str


def parse_version(version_string: str) -> tuple[int, int, int]:
    """
    Parst eine Semver-Version in (major, minor, patch).

    Args:
        version_string: Version String wie "0.2.1"

    Returns:
        Tuple mit (major, minor, patch)
    """
    match = re.match(r"(\d+)\.(\d+)\.(\d+)", version_string)
    if not match:
        raise ValueError(f"Ungültiges Versions-Format: {version_string}")
    return int(match.group(1)), int(match.group(2)), int(match.group(3))


def version_satisfies_constraint(
    actual_version: str, constraint: VersionConstraint
) -> tuple[bool, str]:
    """
    Prüft, ob eine Version einen Constraint erfüllt.

    Args:
        actual_version: Tatsächliche installierte Version
        constraint: Versions-Constraint

    Returns:
        Tuple (erfüllt, fehlermeldung)
    """
    try:
        actual = parse_version(actual_version)
        min_ver = parse_version(constraint.min_version)

        # Prüfe Minimum
        if actual < min_ver:
            return False, (
                f"Version {actual_version} ist kleiner als Minimum "
                f"{constraint.min_version}"
            )

        # Prüfe Maximum (falls vorhanden)
        if constraint.max_version:
            max_ver = parse_version(constraint.max_version)
            if actual >= max_ver:
                return False, (
                    f"Version {actual_version} ist >= Maximum "
                    f"{constraint.max_version}"
                )

        return True, ""
    except ValueError as e:
        return False, str(e)


class TestSubPackageVersions:
    """Tests für Sub-Paket Versionen."""

    def test_authentication_version_installed(self):
        """Test: questra_authentication ist installiert."""
        try:
            auth_version = version("seven2one-questra-authentication")
            assert auth_version is not None
            assert len(auth_version) > 0
        except Exception as e:
            pytest.fail(f"questra_authentication nicht installiert: {e}")

    def test_data_version_installed(self):
        """Test: questra_data ist installiert."""
        try:
            data_version = version("seven2one-questra-data")
            assert data_version is not None
            assert len(data_version) > 0
        except Exception as e:
            pytest.fail(f"questra_data nicht installiert: {e}")

    def test_authentication_version_compatibility(self):
        """Test: questra_authentication Version erfüllt Constraints."""
        # Aus pyproject.toml: seven2one-questra-authentication>=0.1.5,<0.3.0
        constraint = VersionConstraint(
            min_version="0.1.5",
            max_version="0.3.0",
            package_name="seven2one-questra-authentication",
        )

        actual_version = version("seven2one-questra-authentication")
        satisfies, error_msg = version_satisfies_constraint(actual_version, constraint)

        assert satisfies, (
            f"seven2one-questra-authentication Version {actual_version} "
            f"erfüllt Constraint nicht: {error_msg}"
        )

    def test_data_version_compatibility(self):
        """Test: questra_data Version erfüllt Constraints."""
        # Aus pyproject.toml: seven2one-questra-data>=0.7.3
        # Kein Maximum definiert, aber wir warnen bei Breaking Changes (Major > 0)
        constraint = VersionConstraint(
            min_version="0.7.3", max_version=None, package_name="seven2one-questra-data"
        )

        actual_version = version("seven2one-questra-data")

        # Prüfe Minimum
        actual = parse_version(actual_version)
        min_ver = parse_version(constraint.min_version)

        assert actual >= min_ver, (
            f"seven2one-questra-data Version {actual_version} "
            f"ist kleiner als Minimum {constraint.min_version}"
        )


class TestBreakingChangeDetection:
    """Tests zur Erkennung von Breaking Changes."""

    def test_authentication_no_major_version_jump(self):
        """Test: Warnung bei Major Version Jump in authentication."""
        actual_version = version("seven2one-questra-authentication")
        major, _, _ = parse_version(actual_version)

        # Im pyproject.toml ist <0.3.0 definiert, also Major sollte 0 sein
        expected_major = 0
        assert major == expected_major, (
            f"Breaking Change erkannt: authentication Major Version ist {major}, "
            f"erwartet {expected_major}. "
            f"Bitte pyproject.toml Constraints aktualisieren."
        )

    def test_data_major_version_is_reasonable(self):
        """Test: data Major Version ist im erwarteten Bereich."""
        actual_version = version("seven2one-questra-data")
        major, _, _ = parse_version(actual_version)

        # data hat kein oberes Limit, aber Major sollte nicht zu hoch sein
        # (< 2.0 für aktuelles Development)
        assert major < 2, (
            f"Unerwartete Major Version in questra_data: {major}. "
            f"Möglicher Breaking Change - bitte prüfen."
        )


class TestDependencyConsistency:
    """Tests für Konsistenz der Dependencies."""

    def test_authentication_is_consistent_across_packages(self):
        """
        Test: authentication Version ist konsistent.
        """
        # Umbrella Constraint: >=0.1.5,<0.3.0
        # Data Constraint: >=0.2.1,<0.3.0
        # Gemeinsamer Bereich: >=0.2.1,<0.3.0

        actual_version = version("seven2one-questra-authentication")
        actual = parse_version(actual_version)

        # Sollte beide Constraints erfüllen
        umbrella_min = parse_version("0.1.5")
        data_min = parse_version("0.2.1")
        max_ver = parse_version("0.3.0")

        assert actual >= umbrella_min, (
            f"authentication {actual_version} erfüllt nicht Umbrella Minimum 0.1.5"
        )
        assert actual >= data_min, (
            f"authentication {actual_version} erfüllt nicht Data Minimum 0.2.1"
        )
        assert actual < max_ver, (
            f"authentication {actual_version} erfüllt nicht Maximum <0.3.0"
        )

    def test_package_versions_are_semver(self):
        """Test: Alle Paket-Versionen folgen Semver."""
        packages = [
            "seven2one-questra",
            "seven2one-questra-authentication",
            "seven2one-questra-data",
        ]

        for package_name in packages:
            try:
                pkg_version = version(package_name)
                # Sollte parsebar sein
                major, minor, patch = parse_version(pkg_version)
                assert major >= 0
                assert minor >= 0
                assert patch >= 0
            except ValueError as e:
                pytest.fail(f"{package_name} Version ist kein gültiges Semver: {e}")


class TestImportCompatibility:
    """Tests für Import-Kompatibilität zwischen Versionen."""

    def test_critical_imports_available(self):
        """Test: Kritische Imports sind über alle Paket-Versionen verfügbar."""
        # Diese Imports müssen immer funktionieren
        try:
            from questra import QuestraAuthentication, QuestraData

            assert QuestraAuthentication is not None
            assert QuestraData is not None
        except ImportError as e:
            pytest.fail(f"Kritischer Import fehlgeschlagen: {e}")

    def test_authentication_api_stable(self):
        """Test: Authentication API ist stabil über Minor-Versionen."""
        from questra import OAuth2ServiceCredential, QuestraAuthentication

        # Diese API sollte über 0.x.x Versionen stabil bleiben
        # QuestraAuthentication verwendet reauthenticate() statt authenticate()
        # und get_access_token() statt get_token()
        assert hasattr(QuestraAuthentication, "reauthenticate")
        assert hasattr(QuestraAuthentication, "get_access_token")
        assert hasattr(QuestraAuthentication, "is_authenticated")

        # Credentials sollten konstruierbar sein
        cred = OAuth2ServiceCredential(username="test", password="test")
        assert cred.username == "test"

    def test_data_api_stable(self):
        """Test: Data API ist stabil über Minor-Versionen."""
        from questra import QuestraData, QuestraDataCore

        # High-Level API - tatsächliche Methoden
        assert hasattr(QuestraData, "list_items")
        assert hasattr(QuestraData, "create_items")
        assert hasattr(QuestraData, "list_inventories")

        # Low-Level API - Zugriff über lowlevel Property
        assert hasattr(QuestraDataCore, "queries")
        assert hasattr(QuestraDataCore, "mutations")
