"""Tests for BuildService — git archive + docker build (mocked subprocess)."""

from __future__ import annotations

import subprocess
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import pytest

from kuberoku.exceptions import BuildError
from kuberoku.factory import KuberokuFactory
from tests.backends.fake import FakeK8sClient


def _cp(
    cmd: Any = None,
    *,
    stdout: Any = b"",
    stderr: Any = b"",
) -> subprocess.CompletedProcess[Any]:
    """Short helper to build CompletedProcess results."""
    return subprocess.CompletedProcess(
        args=cmd or [],
        returncode=0,
        stdout=stdout,
        stderr=stderr,
    )


@pytest.fixture()
def build_factory(tmp_namespace: str) -> KuberokuFactory:
    return KuberokuFactory(
        k8s_client=FakeK8sClient(),
        namespace=tmp_namespace,
    )


def _mock_subprocess_ok(
    *args: object,
    **kwargs: object,
) -> subprocess.CompletedProcess[bytes]:
    """Mock successful subprocess call."""
    return _cp(stdout=b"abc1234def5678\n")


def _mock_subprocess_text_ok(
    *args: object,
    **kwargs: object,
) -> subprocess.CompletedProcess[str]:
    """Mock successful subprocess call with text output."""
    return _cp(stdout="abc1234def5678\n", stderr="")


def _mock_subprocess_uncommitted(
    *args: object,
    **kwargs: object,
) -> subprocess.CompletedProcess[str]:
    """Mock git status showing uncommitted changes."""
    return _cp(
        stdout=" M file.py\n M other.py\n",
        stderr="",
    )


class TestBuildPrerequisites:
    @patch("kuberoku.sdk.build.subprocess.run")
    def test_no_git_repo(
        self,
        mock_run: MagicMock,
        build_factory: KuberokuFactory,
    ) -> None:
        mock_run.side_effect = subprocess.CalledProcessError(1, "git")
        with pytest.raises(BuildError, match="git"):
            build_factory.build.build("myapp")

    @patch("kuberoku.sdk.build.subprocess.run")
    def test_no_docker(
        self,
        mock_run: MagicMock,
        build_factory: KuberokuFactory,
    ) -> None:
        def side_effect(
            *args: object,
            **kwargs: object,
        ) -> subprocess.CompletedProcess[bytes]:
            cmd = args[0] if args else kwargs.get("args", [])
            if cmd and cmd[0] == "git":  # type: ignore[index]
                return _cp()
            raise subprocess.CalledProcessError(1, "docker")

        mock_run.side_effect = side_effect
        with pytest.raises(BuildError, match="Docker"):
            build_factory.build.build("myapp")

    @patch("kuberoku.sdk.build.Path.is_file", return_value=False)
    @patch("kuberoku.sdk.build.subprocess.run")
    def test_no_dockerfile(
        self,
        mock_run: MagicMock,
        mock_isfile: MagicMock,
        build_factory: KuberokuFactory,
    ) -> None:
        mock_run.return_value = _cp()
        with pytest.raises(BuildError, match="Dockerfile"):
            build_factory.build.build("myapp")


class TestBuildResolveRef:
    @patch("kuberoku.sdk.build.Path.is_file", return_value=True)
    @patch("kuberoku.sdk.build.subprocess.run")
    def test_resolve_head(
        self,
        mock_run: MagicMock,
        mock_isfile: MagicMock,
        build_factory: KuberokuFactory,
    ) -> None:
        # Mock registry to be local
        build_factory.registry._cached_local = True
        build_factory.registry._cached_registry = ""

        full_sha = "abc1234def5678901234567890123456789abcde"

        def side_effect(
            cmd: list[str],
            **kwargs: object,
        ) -> subprocess.CompletedProcess[Any]:
            if cmd[:2] == ["git", "rev-parse"] and cmd[2] == "--git-dir":
                return _cp(cmd, stdout=b".git")
            if cmd[:2] == ["docker", "info"]:
                return _cp(cmd)
            if cmd[:2] == ["git", "rev-parse"] and cmd[2] == "HEAD":
                return _cp(cmd, stdout=f"{full_sha}\n", stderr="")
            if cmd[:2] == ["git", "status"]:
                return _cp(cmd, stdout="", stderr="")
            if cmd[:2] == ["git", "archive"]:
                # Create a dummy archive
                dest = cmd[cmd.index("-o") + 1]
                Path(dest).write_bytes(b"fake tar")
                return _cp(cmd)
            if cmd[:2] == ["docker", "build"]:
                return _cp(cmd)
            return _cp(cmd)

        mock_run.side_effect = side_effect
        result = build_factory.build.build("myapp", ref="HEAD")
        assert result.commit_sha == full_sha
        assert result.ref_name == "HEAD"
        assert "abc1234" in result.image


class TestBuildOnStep:
    @patch("kuberoku.sdk.build.Path.is_file", return_value=True)
    @patch("kuberoku.sdk.build.subprocess.run")
    def test_on_step_progress(
        self,
        mock_run: MagicMock,
        mock_isfile: MagicMock,
        build_factory: KuberokuFactory,
    ) -> None:
        build_factory.registry._cached_local = True
        build_factory.registry._cached_registry = ""

        def side_effect(
            cmd: list[str],
            **kwargs: object,
        ) -> subprocess.CompletedProcess[Any]:
            if cmd[:2] == ["git", "rev-parse"] and "--git-dir" in cmd:
                return _cp(cmd, stdout=b".git")
            if cmd[:2] == ["docker", "info"]:
                return _cp(cmd)
            if cmd[:2] == ["git", "rev-parse"]:
                return _cp(cmd, stdout="abc1234def5678\n", stderr="")
            if cmd[:2] == ["git", "status"]:
                return _cp(cmd, stdout="", stderr="")
            if cmd[:2] == ["git", "archive"]:
                dest = cmd[cmd.index("-o") + 1]
                Path(dest).write_bytes(b"fake tar")
                return _cp(cmd)
            if cmd[:2] == ["docker", "build"]:
                return _cp(cmd)
            return _cp(cmd)

        mock_run.side_effect = side_effect
        steps: list[str] = []
        build_factory.build.build("myapp", on_step=steps.append)
        assert len(steps) > 0
        assert any("Resolving" in s or "Building" in s or "build" in s.lower() for s in steps)


class TestBuildFailures:
    @patch("kuberoku.sdk.build.Path.is_file", return_value=True)
    @patch("kuberoku.sdk.build.subprocess.run")
    def test_git_archive_failure(
        self,
        mock_run: MagicMock,
        mock_isfile: MagicMock,
        build_factory: KuberokuFactory,
    ) -> None:
        build_factory.registry._cached_local = True
        build_factory.registry._cached_registry = ""

        call_count = 0

        def side_effect(
            cmd: list[str],
            **kwargs: object,
        ) -> subprocess.CompletedProcess[Any]:
            nonlocal call_count
            call_count += 1
            if cmd[:2] == ["git", "rev-parse"] and "--git-dir" in cmd:
                return _cp(cmd, stdout=b".git")
            if cmd[:2] == ["docker", "info"]:
                return _cp(cmd)
            if cmd[:2] == ["git", "rev-parse"]:
                return _cp(cmd, stdout="abc1234\n", stderr="")
            if cmd[:2] == ["git", "status"]:
                return _cp(cmd, stdout="", stderr="")
            if cmd[:2] == ["git", "archive"]:
                raise subprocess.CalledProcessError(
                    1,
                    "git archive",
                    stderr=b"archive failed",
                )
            return _cp(cmd)

        mock_run.side_effect = side_effect
        with pytest.raises(BuildError, match="archive"):
            build_factory.build.build("myapp")
