from __future__ import annotations

import contextlib
import io
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from PIL import Image

from src.cli import main
from src.errors import UserError


class FakeCwebpTool:
    def __init__(self) -> None:
        self.qualities: list[int] = []

    def run(self, *, input_path: Path, output_path: Path, quality: int) -> None:
        _ = input_path
        self.qualities.append(int(quality))
        size = 250 * 1024 if quality >= 80 else 180 * 1024
        output_path.write_bytes(b"x" * size)


class TestImageExports(unittest.TestCase):
    def test_website_dry_run_does_not_require_cwebp_or_write_files(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            inbox = Path(tmp) / "inbox"
            out = Path(tmp) / "out"
            inbox.mkdir(parents=True, exist_ok=True)
            (inbox / "a.jpg").write_bytes(b"not-a-real-image")

            stdout = io.StringIO()
            with (
                patch.dict(os.environ, {}, clear=True),
                patch("src.images.website_webp.require_cwebp", side_effect=AssertionError("require_cwebp called")),
                contextlib.redirect_stdout(stdout),
            ):
                code = main(["export", "website", "--inbox", str(inbox), "--out", str(out), "--dry-run"])

            self.assertEqual(code, 0)
            self.assertIn("files_found: 1", stdout.getvalue())
            self.assertFalse((out / "website").exists())

    def test_website_run_fails_closed_without_cwebp(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            inbox = Path(tmp) / "inbox"
            out = Path(tmp) / "out"
            inbox.mkdir(parents=True, exist_ok=True)
            (inbox / "a.jpg").write_bytes(b"not-a-real-image")

            stderr = io.StringIO()
            with (
                patch.dict(os.environ, {}, clear=True),
                patch("src.images.website_webp.require_cwebp", side_effect=UserError("Missing dependency: cwebp")),
                contextlib.redirect_stderr(stderr),
            ):
                code = main(["export", "website", "--inbox", str(inbox), "--out", str(out)])

            self.assertEqual(code, 2)
            self.assertIn("Missing dependency: cwebp", stderr.getvalue())
            self.assertFalse((out / "website").exists())

    def test_website_run_writes_webp_with_fake_cwebp(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            inbox = Path(tmp) / "inbox"
            out = Path(tmp) / "out"
            (inbox / "sub").mkdir(parents=True, exist_ok=True)
            (inbox / "sub" / "a.jpg").write_bytes(b"not-a-real-image")

            fake = FakeCwebpTool()
            stdout = io.StringIO()
            with (
                patch.dict(os.environ, {}, clear=True),
                patch("src.images.website_webp.require_cwebp", return_value=fake),
                contextlib.redirect_stdout(stdout),
            ):
                code = main(["export", "website", "--inbox", str(inbox), "--out", str(out)])

            self.assertEqual(code, 0)
            dest = out / "website" / "sub" / "a.webp"
            self.assertTrue(dest.exists())
            self.assertLessEqual(dest.stat().st_size, 200 * 1024)
            self.assertFalse(dest.with_suffix(dest.suffix + ".tmp").exists())
            self.assertEqual(fake.qualities[:2], [80, 70])

    def test_gbp_run_writes_jpg_and_png_and_enforces_min_size(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            inbox = Path(tmp) / "inbox"
            out = Path(tmp) / "out"
            inbox.mkdir(parents=True, exist_ok=True)

            src = inbox / "a.b.jpg"
            Image.new("RGB", (100, 120), color=(255, 0, 0)).save(src, format="JPEG")

            stdout = io.StringIO()
            with patch.dict(os.environ, {}, clear=True), contextlib.redirect_stdout(stdout):
                code = main(["export", "gbp", "--inbox", str(inbox), "--out", str(out)])

            self.assertEqual(code, 0)
            dest_jpg = out / "gbp" / "a.b.jpg"
            dest_png = out / "gbp" / "a.b.png"
            self.assertTrue(dest_jpg.exists())
            self.assertTrue(dest_png.exists())
            with Image.open(dest_jpg) as img_jpg, Image.open(dest_png) as img_png:
                self.assertGreaterEqual(img_jpg.size[0], 250)
                self.assertGreaterEqual(img_jpg.size[1], 250)
                self.assertGreaterEqual(img_png.size[0], 250)
                self.assertGreaterEqual(img_png.size[1], 250)

    def test_gbp_dry_run_does_not_write_files(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            inbox = Path(tmp) / "inbox"
            out = Path(tmp) / "out"
            inbox.mkdir(parents=True, exist_ok=True)

            src = inbox / "a.b.jpg"
            Image.new("RGB", (100, 120), color=(255, 0, 0)).save(src, format="JPEG")

            stdout = io.StringIO()
            with patch.dict(os.environ, {}, clear=True), contextlib.redirect_stdout(stdout):
                code = main(["export", "gbp", "--inbox", str(inbox), "--out", str(out), "--dry-run"])

            self.assertEqual(code, 0)
            self.assertFalse((out / "gbp").exists())
