"""Tests for axm-image operations — TDD RED phase."""

from pathlib import Path

import numpy as np
import pytest

from axm_image.operations import (
    CropOperation,
    LoadOperation,
    ResizeOperation,
    SaveOperation,
)


@pytest.fixture
def sample_image() -> np.ndarray:
    """Create a sample RGB image for testing."""
    return np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)


@pytest.fixture
def temp_image(tmp_path: Path, sample_image: np.ndarray) -> Path:
    """Create a temporary image file."""
    import cv2

    filepath = tmp_path / "test_image.png"
    cv2.imwrite(str(filepath), cv2.cvtColor(sample_image, cv2.COLOR_RGB2BGR))
    return filepath


class TestLoadOperation:
    """Tests for LoadOperation."""

    def test_load_operation_name(self) -> None:
        """LoadOperation has correct name."""
        op = LoadOperation()
        assert op.name == "image.load"

    def test_load_image_success(self, temp_image: Path) -> None:
        """Can load an image from disk."""
        op = LoadOperation()
        result = op.execute(
            data=None,
            context={"path": str(temp_image)},
            config={},
        )
        assert result.success is True
        assert result.data is not None
        assert result.data.shape == (100, 100, 3)

    def test_load_image_file_not_found(self, tmp_path: Path) -> None:
        """Returns failure for missing file."""
        op = LoadOperation()
        result = op.execute(
            data=None,
            context={"path": str(tmp_path / "nonexistent.png")},
            config={},
        )
        assert result.success is False
        assert "not found" in result.error.lower()


class TestSaveOperation:
    """Tests for SaveOperation."""

    def test_save_operation_name(self) -> None:
        """SaveOperation has correct name."""
        op = SaveOperation()
        assert op.name == "image.save"

    def test_save_image_success(self, sample_image: np.ndarray, tmp_path: Path) -> None:
        """Can save an image to disk."""
        op = SaveOperation()
        output_path = tmp_path / "output.png"
        result = op.execute(
            data=sample_image,
            context={"output_path": str(output_path)},
            config={},
        )
        assert result.success is True
        assert output_path.exists()

    def test_save_no_image_fails(self, tmp_path: Path) -> None:
        """Returns failure when no image provided."""
        op = SaveOperation()
        result = op.execute(
            data=None,
            context={"output_path": str(tmp_path / "out.png")},
            config={},
        )
        assert result.success is False
        assert "no input image" in result.error.lower()


class TestCropOperation:
    """Tests for CropOperation."""

    def test_crop_operation_name(self) -> None:
        """CropOperation has correct name."""
        op = CropOperation()
        assert op.name == "image.crop"

    def test_crop_image_success(self, sample_image: np.ndarray) -> None:
        """Can crop an image."""
        op = CropOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"x": 10, "y": 10, "width": 50, "height": 50},
        )
        assert result.success is True
        assert result.data.shape == (50, 50, 3)

    def test_crop_out_of_bounds_fails(self, sample_image: np.ndarray) -> None:
        """Returns failure for out-of-bounds crop."""
        op = CropOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"x": 90, "y": 90, "width": 50, "height": 50},
        )
        assert result.success is False
        assert "bounds" in result.error.lower()

    def test_crop_no_input_fails(self) -> None:
        """Returns failure when no input image provided."""
        op = CropOperation()
        result = op.execute(
            data=None,
            context={},
            config={"x": 0, "y": 0, "width": 50, "height": 50},
        )
        assert result.success is False
        assert "no input" in result.error.lower()

    def test_crop_invalid_params_fails(self, sample_image: np.ndarray) -> None:
        """Returns failure for invalid crop parameters (negative/zero values)."""
        op = CropOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"x": -1, "y": 0, "width": 50, "height": 50},
        )
        assert result.success is False
        assert "positive" in result.error.lower() or "invalid" in result.error.lower()


class TestResizeOperation:
    """Tests for ResizeOperation."""

    def test_resize_operation_name(self) -> None:
        """ResizeOperation has correct name."""
        op = ResizeOperation()
        assert op.name == "image.resize"

    def test_resize_image_success(self, sample_image: np.ndarray) -> None:
        """Can resize an image."""
        op = ResizeOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"width": 50, "height": 50},
        )
        assert result.success is True
        assert result.data.shape == (50, 50, 3)

    def test_resize_by_scale(self, sample_image: np.ndarray) -> None:
        """Can resize by scale factor."""
        op = ResizeOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"scale": 0.5},
        )
        assert result.success is True
        assert result.data.shape == (50, 50, 3)

    def test_resize_width_only(self, sample_image: np.ndarray) -> None:
        """Should calculate height proportionally when only width is given."""
        # Input 100x100, target width 50 -> expected height 50
        op = ResizeOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"width": 50},
        )
        assert result.success is True
        assert result.data.shape == (50, 50, 3)

    def test_resize_height_only(self, sample_image: np.ndarray) -> None:
        """Should calculate width proportionally when only height is given."""
        # Input 100x100, target height 25 -> expected width 25
        op = ResizeOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={"height": 25},
        )
        assert result.success is True
        assert result.data.shape == (25, 25, 3)

    def test_resize_no_params(self, sample_image: np.ndarray) -> None:
        """Should fail if no dimensions provided."""
        op = ResizeOperation()
        result = op.execute(
            data=sample_image,
            context={},
            config={},
        )
        assert result.success is False
        assert "must specify" in result.error.lower()
