import io

import pytest
from PIL import Image
from pydantic import ValidationError

from gui_agent_screenshot_tools import (
    BBox,
    Coordinate,
    InvalidResizeModeError,
    MissingResizeMetadataError,
    ResizeMode,
    Screenshot,
    Space,
    SpaceMismatchError,
)


class TestScreenshotCreation:
    def test_stores_bytes_without_decoding(self, sample_image_bytes, hd_space):
        s = Screenshot(image_bytes=sample_image_bytes, space=hd_space)
        assert s.image_bytes == sample_image_bytes
        # image property not accessed yet - just check bytes stored
        assert "__dict__" not in s.__dict__ or "image" not in s.__dict__

    def test_space_stored(self, sample_screenshot, hd_space):
        assert sample_screenshot.space == hd_space

    def test_no_metadata_by_default(self, sample_screenshot):
        assert sample_screenshot.resize_metadata is None

    def test_space_must_match_image_dimensions(self, sample_image_bytes):
        with pytest.raises(ValidationError):
            Screenshot(
                image_bytes=sample_image_bytes,
                space=Space(width=1280, height=720),
            )


class TestScreenshotImage:
    def test_lazy_decode(self, sample_screenshot):
        img = sample_screenshot.image
        assert isinstance(img, Image.Image)
        assert img.width == 1920
        assert img.height == 1080

    def test_cached(self, sample_screenshot):
        img1 = sample_screenshot.image
        img2 = sample_screenshot.image
        assert img1 is img2


class TestScreenshotFromImage:
    def test_creates_from_pil_image(self):
        img = Image.new("RGB", (800, 600), color=(255, 0, 0))
        s = Screenshot.from_image(img)
        assert s.space == Space(width=800, height=600)
        assert len(s.image_bytes) > 0

    def test_roundtrip_dimensions(self):
        img = Image.new("RGB", (640, 480))
        s = Screenshot.from_image(img)
        decoded = s.image
        assert decoded.width == 640
        assert decoded.height == 480


class TestScreenshotResizeStretch:
    def test_dimensions(self, sample_screenshot):
        target = Space(width=512, height=512)
        resized = sample_screenshot.resize(target, ResizeMode.STRETCH)
        assert resized.space == target
        assert resized.image.width == 512
        assert resized.image.height == 512

    def test_metadata_populated(self, sample_screenshot):
        target = Space(width=512, height=512)
        resized = sample_screenshot.resize(target, ResizeMode.STRETCH)
        assert resized.resize_metadata is not None
        assert resized.resize_metadata.mode == ResizeMode.STRETCH

    def test_original_unmodified(self, sample_screenshot, hd_space):
        target = Space(width=512, height=512)
        sample_screenshot.resize(target, ResizeMode.STRETCH)
        assert sample_screenshot.space == hd_space
        assert sample_screenshot.resize_metadata is None

    def test_invalid_mode_rejected(self, sample_screenshot):
        with pytest.raises(InvalidResizeModeError):
            sample_screenshot.resize(Space(width=512, height=512), "stretch")


class TestScreenshotResizeLetterbox:
    def test_dimensions(self, sample_screenshot):
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        assert resized.space == target
        assert resized.image.width == 1024
        assert resized.image.height == 1024

    def test_metadata_populated(self, sample_screenshot):
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        assert resized.resize_metadata is not None
        assert resized.resize_metadata.mode == ResizeMode.LETTERBOX
        assert resized.resize_metadata.offset_y > 0


class TestLetterboxBlackBarPixels:
    """Verify padding regions contain black (0,0,0) pixels."""

    def test_top_bar_is_black(self, sample_screenshot):
        """HD → square: top padding should be pure black."""
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        img = resized.image
        offset_y = resized.resize_metadata.offset_y
        # Sample pixels across the top bar
        for x in (0, 512, 1023):
            for y in (0, offset_y - 1):
                assert img.getpixel((x, y)) == (0, 0, 0), f"pixel ({x},{y}) not black"

    def test_bottom_bar_is_black(self, sample_screenshot):
        """HD → square: bottom padding should be pure black."""
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        img = resized.image
        meta = resized.resize_metadata
        bottom_start = meta.offset_y + meta.scaled_height
        for x in (0, 512, 1023):
            for y in (bottom_start, 1023):
                assert img.getpixel((x, y)) == (0, 0, 0), f"pixel ({x},{y}) not black"

    def test_content_region_is_not_black(self, sample_screenshot):
        """The content area should contain the original image data, not black."""
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        img = resized.image
        meta = resized.resize_metadata
        # Center of content region
        cx = meta.offset_x + meta.scaled_width // 2
        cy = meta.offset_y + meta.scaled_height // 2
        assert img.getpixel((cx, cy)) != (0, 0, 0)

    def test_side_bars_are_black(self, mobile_screenshot):
        """Mobile → XGA: left/right padding should be pure black."""
        target = Space(width=1024, height=768)
        resized = mobile_screenshot.resize(target, ResizeMode.LETTERBOX)
        img = resized.image
        meta = resized.resize_metadata
        # Left bar
        for y in (0, 384, 767):
            for x in (0, meta.offset_x - 1):
                assert img.getpixel((x, y)) == (0, 0, 0), f"left bar pixel ({x},{y}) not black"
        # Right bar
        right_start = meta.offset_x + meta.scaled_width
        for y in (0, 384, 767):
            for x in (right_start, 1023):
                assert img.getpixel((x, y)) == (0, 0, 0), f"right bar pixel ({x},{y}) not black"

    def test_mobile_content_not_black(self, mobile_screenshot):
        """Mobile → XGA: content area should not be black."""
        target = Space(width=1024, height=768)
        resized = mobile_screenshot.resize(target, ResizeMode.LETTERBOX)
        img = resized.image
        meta = resized.resize_metadata
        cx = meta.offset_x + meta.scaled_width // 2
        cy = meta.offset_y + meta.scaled_height // 2
        assert img.getpixel((cx, cy)) != (0, 0, 0)


class TestScreenshotRemapHelpers:
    def test_map_coord_to_original(self, sample_screenshot):
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)

        coord_in_resized = Coordinate(x=512, y=512, space=resized.space)
        via_helper = resized.map_coord_to_original(coord_in_resized)
        via_metadata = coord_in_resized.to_source_space(resized.resize_metadata)

        assert via_helper == via_metadata

    def test_map_coord_from_original(self, sample_screenshot):
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        coord_in_original = Coordinate(x=960, y=540, space=sample_screenshot.space)

        mapped = resized.map_coord_from_original(coord_in_original)
        assert mapped.space == resized.space

    def test_map_bbox_to_original_with_offset(self, sample_screenshot):
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        screen = Space(width=2560, height=1440)
        window = BBox(x=50, y=100, width=1920, height=1080, space=screen)
        detection = BBox(x=300, y=350, width=120, height=80, space=resized.space)

        via_helper = resized.map_bbox_to_original(detection, offset=window)
        via_manual = detection.to_source_space(
            resized.resize_metadata,
            offset=window,
        )

        assert via_helper == via_manual

    def test_map_coord_to_original_requires_metadata(self, sample_screenshot):
        coord = Coordinate(x=100, y=100, space=sample_screenshot.space)
        with pytest.raises(MissingResizeMetadataError):
            sample_screenshot.map_coord_to_original(coord)

    def test_map_coord_to_original_validates_coord_space(self, sample_screenshot):
        target = Space(width=1024, height=1024)
        resized = sample_screenshot.resize(target, ResizeMode.LETTERBOX)
        wrong_space_coord = Coordinate(x=100, y=100, space=sample_screenshot.space)
        with pytest.raises(SpaceMismatchError):
            resized.map_coord_to_original(wrong_space_coord)
