"""Docker-based E2E test fixtures for install.sh."""

from __future__ import annotations

import hashlib
import json
import shutil
import subprocess
import tarfile
from dataclasses import dataclass
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
DOCKERFILE = Path(__file__).resolve().parent / "Dockerfile.base"
INSTALL_SH = REPO_ROOT / "installer" / "install.sh"
IMAGE_TAG = "ilum-e2e-installer"
MOCK_VERSION = "0.1.0"
_DOWNLOAD_PATH = "ilum-cloud/ilum-cli/releases/download"


@dataclass(frozen=True)
class ContainerResult:
    """Result from running a command inside a Docker container."""

    exit_code: int
    stdout: str
    stderr: str

    @property
    def output(self) -> str:
        return self.stdout + self.stderr


def _docker_available() -> bool:
    """Check if Docker daemon is reachable."""
    if not shutil.which("docker"):
        return False
    try:
        subprocess.run(
            ["docker", "info"],
            capture_output=True,
            timeout=10,
        )
        return True
    except (subprocess.SubprocessError, FileNotFoundError):
        return False


skip_no_docker = pytest.mark.skipif(
    not _docker_available(),
    reason="Docker not available",
)


@pytest.fixture(scope="session")
def docker_image() -> str:
    """Build the E2E Docker image once per session. Returns the image tag."""
    if not _docker_available():
        pytest.skip("Docker not available")

    subprocess.run(
        [
            "docker",
            "build",
            "-f",
            str(DOCKERFILE),
            "-t",
            IMAGE_TAG,
            str(DOCKERFILE.parent),
        ],
        check=True,
        capture_output=True,
        timeout=120,
    )
    return IMAGE_TAG


@pytest.fixture()
def mock_release(tmp_path: Path) -> Path:
    """Create a mock release directory with tarball, SHA256SUMS, and API response.

    Directory layout:
        repos/ilum-cloud/ilum-cli/releases/latest          (GitHub API response)
        ilum-cloud/ilum-cli/releases/download/cli-v0.1.0/   (tarball + checksums)
    """
    # Build stub binary
    stub = tmp_path / "build" / "ilum"
    stub.parent.mkdir(parents=True)
    stub.write_text('#!/bin/bash\necho "ilum 0.1.0"\n')
    stub.chmod(0o755)

    # Create tarball
    platform = "linux-amd64"
    tarball_name = f"ilum-{platform}.tar.gz"
    download_dir = tmp_path / "srv" / _DOWNLOAD_PATH / f"cli-v{MOCK_VERSION}"
    download_dir.mkdir(parents=True)
    tarball_path = download_dir / tarball_name
    with tarfile.open(tarball_path, "w:gz") as tar:
        tar.add(str(stub), arcname="ilum")

    # SHA256SUMS
    sha = hashlib.sha256(tarball_path.read_bytes()).hexdigest()
    (download_dir / "SHA256SUMS").write_text(f"{sha}  {tarball_name}\n")

    # GitHub API latest release response
    api_dir = tmp_path / "srv" / "repos" / "ilum-cloud" / "ilum-cli" / "releases"
    api_dir.mkdir(parents=True)
    (api_dir / "latest").write_text(json.dumps({"tag_name": f"cli-v{MOCK_VERSION}"}))

    return tmp_path / "srv"


@pytest.fixture()
def mock_release_bad_checksum(tmp_path: Path) -> Path:
    """Like mock_release but with a tampered SHA256SUMS."""
    stub = tmp_path / "build" / "ilum"
    stub.parent.mkdir(parents=True)
    stub.write_text('#!/bin/bash\necho "ilum 0.1.0"\n')
    stub.chmod(0o755)

    platform = "linux-amd64"
    tarball_name = f"ilum-{platform}.tar.gz"
    download_dir = tmp_path / "srv" / _DOWNLOAD_PATH / f"cli-v{MOCK_VERSION}"
    download_dir.mkdir(parents=True)
    tarball_path = download_dir / tarball_name
    with tarfile.open(tarball_path, "w:gz") as tar:
        tar.add(str(stub), arcname="ilum")

    # Bad checksum
    (download_dir / "SHA256SUMS").write_text(f"{'0' * 64}  {tarball_name}\n")

    api_dir = tmp_path / "srv" / "repos" / "ilum-cloud" / "ilum-cli" / "releases"
    api_dir.mkdir(parents=True)
    (api_dir / "latest").write_text(json.dumps({"tag_name": f"cli-v{MOCK_VERSION}"}))

    return tmp_path / "srv"


@pytest.fixture()
def mock_release_no_tarball(tmp_path: Path) -> Path:
    """Like mock_release but tarball is missing (SHA256SUMS only)."""
    platform = "linux-amd64"
    tarball_name = f"ilum-{platform}.tar.gz"
    download_dir = tmp_path / "srv" / _DOWNLOAD_PATH / f"cli-v{MOCK_VERSION}"
    download_dir.mkdir(parents=True)

    (download_dir / "SHA256SUMS").write_text(f"{'a' * 64}  {tarball_name}\n")

    api_dir = tmp_path / "srv" / "repos" / "ilum-cloud" / "ilum-cli" / "releases"
    api_dir.mkdir(parents=True)
    (api_dir / "latest").write_text(json.dumps({"tag_name": f"cli-v{MOCK_VERSION}"}))

    return tmp_path / "srv"


def _run_container(
    image: str,
    release_dir: Path,
    cmd: str,
    *,
    user: str = "testuser",
    env: dict[str, str] | None = None,
) -> ContainerResult:
    """Run a command inside a Docker container with the mock server."""
    docker_env: list[str] = []
    base_env = {
        "ILUM_DOWNLOAD_BASE": "http://localhost:8080",
        "ILUM_GITHUB_API": "http://localhost:8080",
    }
    if env:
        base_env.update(env)
    for k, v in base_env.items():
        docker_env.extend(["-e", f"{k}={v}"])

    # Wrap command: start HTTP server in background, wait, run user command
    wrapper = (
        "python3 -m http.server 8080 --directory /srv/releases > /dev/null 2>&1 & "
        "sleep 0.5 && "
        f"{cmd}"
    )

    proc = subprocess.run(
        [
            "docker",
            "run",
            "--rm",
            "-v",
            f"{release_dir}:/srv/releases:ro",
            "-v",
            f"{INSTALL_SH}:/workspace/install.sh:ro",
            *docker_env,
            "-u",
            user,
            image,
            "bash",
            "-c",
            wrapper,
        ],
        capture_output=True,
        text=True,
        timeout=60,
    )
    return ContainerResult(
        exit_code=proc.returncode,
        stdout=proc.stdout,
        stderr=proc.stderr,
    )


@pytest.fixture()
def run_in_container(docker_image: str, mock_release: Path):  # noqa: ANN201
    """Callable fixture to run commands in a container with mock release server."""

    def _run(
        cmd: str,
        *,
        user: str = "testuser",
        env: dict[str, str] | None = None,
    ) -> ContainerResult:
        return _run_container(docker_image, mock_release, cmd, user=user, env=env)

    return _run


@pytest.fixture()
def run_in_container_bad_checksum(docker_image: str, mock_release_bad_checksum: Path):  # noqa: ANN201
    """Callable fixture with a tampered checksum."""

    def _run(
        cmd: str,
        *,
        user: str = "testuser",
        env: dict[str, str] | None = None,
    ) -> ContainerResult:
        return _run_container(docker_image, mock_release_bad_checksum, cmd, user=user, env=env)

    return _run


@pytest.fixture()
def run_in_container_no_tarball(docker_image: str, mock_release_no_tarball: Path):  # noqa: ANN201
    """Callable fixture with missing tarball."""

    def _run(
        cmd: str,
        *,
        user: str = "testuser",
        env: dict[str, str] | None = None,
    ) -> ContainerResult:
        return _run_container(docker_image, mock_release_no_tarball, cmd, user=user, env=env)

    return _run
