"""Integration tests for /engine/trace and /engine/simplify endpoints."""

import json

import cv2
import numpy as np
import pytest
from fastapi.testclient import TestClient

from main import app

client = TestClient(app)


def _make_test_png(width: int = 100, height: int = 100) -> bytes:
    """Create a simple test PNG with a white rectangle on black background."""
    img = np.zeros((height, width, 3), dtype=np.uint8)
    cv2.rectangle(img, (20, 20), (80, 80), (255, 255, 255), -1)
    ok, buf = cv2.imencode(".png", img)
    assert ok
    return buf.tobytes()


def _make_test_svg() -> str:
    """Create a simple SVG with a rectangular path for simplify tests."""
    return (
        '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">'
        '<path d="M 10,10 L 90,10 L 90,90 L 10,90 Z" fill="black"/>'
        "</svg>"
    )


def _make_complex_svg() -> str:
    """Create an SVG with many intermediate points (suitable for RDP reduction)."""
    # A path with extra collinear intermediate points that RDP can remove
    points = " ".join(
        f"L {x},{10}" for x in range(11, 91)
    )
    return (
        '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">'
        f'<path d="M 10,10 {points} L 90,90 L 10,90 Z" fill="black"/>'
        "</svg>"
    )


@pytest.fixture
def test_png() -> bytes:
    return _make_test_png()


@pytest.fixture
def test_svg() -> bytes:
    return _make_test_svg().encode("utf-8")


@pytest.fixture
def complex_svg() -> bytes:
    return _make_complex_svg().encode("utf-8")


# -----------------------------------------------------------------------
# /engine/trace — SVG output (default)
# -----------------------------------------------------------------------

class TestTraceEndpointSVG:
    """Tests for /engine/trace with SVG output format."""

    def test_basic_trace(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace"},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["format"] == "svg"
        assert "<svg" in body["output"]
        assert "metadata" in body

    def test_metadata_shape(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace"},
        )
        body = resp.json()
        meta = body["metadata"]
        assert meta["format"] == "svg"
        assert "path_count" in meta
        assert "node_count_total" in meta
        assert "open_paths" in meta
        assert "island_count" in meta
        assert "warnings" in meta
        assert "processing_ms" in meta
        assert isinstance(meta["path_count"], int)
        assert isinstance(meta["node_count_total"], int)
        assert isinstance(meta["processing_ms"], float)

    def test_svg_has_paths(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace"},
        )
        body = resp.json()
        assert body["metadata"]["path_count"] >= 1

    def test_custom_params(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={
                "mode": "potrace",
                "params": json.dumps({"turdsize": 5, "alphamax": 0.5}),
            },
        )
        assert resp.status_code == 200
        assert "<svg" in resp.json()["output"]

    def test_defaults_to_potrace_svg(self, test_png):
        """Mode defaults to potrace, format defaults to svg."""
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["format"] == "svg"
        assert "<svg" in body["output"]


# -----------------------------------------------------------------------
# /engine/trace — JSON output
# -----------------------------------------------------------------------

class TestTraceEndpointJSON:
    """Tests for /engine/trace with JSON output format."""

    def test_json_output(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "json"},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["format"] == "json"
        assert "output" in body
        assert "paths" in body["output"]
        assert "metadata" in body["output"]

    def test_json_metadata_fields(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "json"},
        )
        body = resp.json()
        # Envelope metadata
        assert body["metadata"]["format"] == "json"
        assert isinstance(body["metadata"]["node_count_total"], int)
        # Inline JSON metadata
        inner_meta = body["output"]["metadata"]
        assert "path_count" in inner_meta
        assert "total_nodes" in inner_meta

    def test_json_path_structure(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "json"},
        )
        body = resp.json()
        paths = body["output"]["paths"]
        assert len(paths) >= 1
        p = paths[0]
        assert "commands" in p
        assert "properties" in p
        assert p["commands"][0]["type"] == "M"  # starts with MoveTo

    def test_json_vtracer(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "vtracer", "output_format": "json"},
        )
        assert resp.status_code == 200
        assert resp.json()["format"] == "json"


# -----------------------------------------------------------------------
# /engine/trace — DXF output
# -----------------------------------------------------------------------

class TestTraceEndpointDXF:
    """Tests for /engine/trace with DXF output format."""

    def test_dxf_output(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "dxf"},
        )
        assert resp.status_code == 200
        assert resp.headers["content-type"] == "application/dxf"
        assert "Content-Disposition" in resp.headers
        assert len(resp.content) > 0

    def test_dxf_metadata_header(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "dxf"},
        )
        meta_header = resp.headers.get("X-Kerf-Metadata")
        assert meta_header is not None
        meta = json.loads(meta_header)
        assert meta["format"] == "dxf"
        assert "path_count" in meta
        assert "node_count_total" in meta

    def test_dxf_is_valid_ac1015(self, test_png):
        """DXF output should be parseable by ezdxf as AC1015."""
        import io
        import ezdxf

        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "dxf"},
        )
        stream = io.StringIO(resp.content.decode("utf-8"))
        doc = ezdxf.read(stream)
        assert doc.dxfversion == "AC1015"
        msp = doc.modelspace()
        entities = list(msp)
        assert len(entities) >= 1

    def test_dxf_vtracer(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "vtracer", "output_format": "dxf"},
        )
        assert resp.status_code == 200
        assert resp.headers["content-type"] == "application/dxf"


# -----------------------------------------------------------------------
# /engine/trace — Validation
# -----------------------------------------------------------------------

class TestTraceEndpointValidation:
    """Tests for input validation on /engine/trace."""

    def test_invalid_mode(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "invalid"},
        )
        assert resp.status_code == 422

    def test_unsupported_output_format(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "output_format": "pdf"},
        )
        assert resp.status_code == 422

    def test_invalid_params_json(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "params": "not-json"},
        )
        assert resp.status_code == 422

    def test_empty_file(self):
        resp = client.post(
            "/engine/trace",
            files={"file": ("empty.png", b"", "image/png")},
            data={"mode": "potrace"},
        )
        assert resp.status_code == 422

    def test_corrupt_image(self):
        resp = client.post(
            "/engine/trace",
            files={"file": ("bad.png", b"not an image", "image/png")},
            data={"mode": "potrace"},
        )
        assert resp.status_code == 422

    def test_unknown_preset_rejected(self, test_png):
        """Unknown preset names are rejected with 422."""
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "potrace", "preset": "logo"},
        )
        assert resp.status_code == 422


# -----------------------------------------------------------------------
# /engine/trace — VTracer mode
# -----------------------------------------------------------------------

class TestTraceEndpointVtracer:
    """Tests for /engine/trace with mode=vtracer."""

    def test_basic_trace(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "vtracer"},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["format"] == "svg"

    def test_metadata_present(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={"mode": "vtracer"},
        )
        body = resp.json()
        meta = body["metadata"]
        assert isinstance(meta["path_count"], int)
        assert isinstance(meta["processing_ms"], float)

    def test_custom_params(self, test_png):
        resp = client.post(
            "/engine/trace",
            files={"file": ("test.png", test_png, "image/png")},
            data={
                "mode": "vtracer",
                "params": json.dumps({"filter_speckle": 10, "corner_threshold": 90}),
            },
        )
        assert resp.status_code == 200


# -----------------------------------------------------------------------
# /engine/simplify — SVG output
# -----------------------------------------------------------------------

class TestSimplifyEndpointSVG:
    """Tests for /engine/simplify with SVG output format."""

    def test_basic_simplify(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0"},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["format"] == "svg"
        assert "<svg" in body["output"]

    def test_simplify_metadata(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0"},
        )
        body = resp.json()
        meta = body["metadata"]
        assert meta["format"] == "svg"
        assert "path_count" in meta
        assert "node_count_total" in meta
        assert "open_paths" in meta
        assert "island_count" in meta
        assert "processing_ms" in meta

    def test_simplify_reduces_nodes(self, complex_svg):
        """Higher epsilon should reduce node count on complex paths."""
        resp_low = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", complex_svg, "image/svg+xml")},
            data={"epsilon": "0.01"},
        )
        resp_high = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", complex_svg, "image/svg+xml")},
            data={"epsilon": "10.0"},
        )
        low_nodes = resp_low.json()["metadata"]["node_count_total"]
        high_nodes = resp_high.json()["metadata"]["node_count_total"]
        assert high_nodes <= low_nodes


# -----------------------------------------------------------------------
# /engine/simplify — JSON output
# -----------------------------------------------------------------------

class TestSimplifyEndpointJSON:
    """Tests for /engine/simplify with JSON output format."""

    def test_json_output(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0", "output_format": "json"},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["format"] == "json"
        assert "paths" in body["output"]

    def test_json_path_commands(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0", "output_format": "json"},
        )
        body = resp.json()
        paths = body["output"]["paths"]
        assert len(paths) >= 1
        assert paths[0]["commands"][0]["type"] == "M"


# -----------------------------------------------------------------------
# /engine/simplify — DXF output
# -----------------------------------------------------------------------

class TestSimplifyEndpointDXF:
    """Tests for /engine/simplify with DXF output format."""

    def test_dxf_output(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0", "output_format": "dxf"},
        )
        assert resp.status_code == 200
        assert resp.headers["content-type"] == "application/dxf"
        assert len(resp.content) > 0

    def test_dxf_metadata_header(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0", "output_format": "dxf"},
        )
        meta = json.loads(resp.headers["X-Kerf-Metadata"])
        assert meta["format"] == "dxf"
        assert isinstance(meta["path_count"], int)

    def test_dxf_is_valid(self, test_svg):
        """DXF output from simplify should be parseable."""
        import io
        import ezdxf

        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0", "output_format": "dxf"},
        )
        stream = io.StringIO(resp.content.decode("utf-8"))
        doc = ezdxf.read(stream)
        assert doc.dxfversion == "AC1015"


# -----------------------------------------------------------------------
# /engine/simplify — Validation
# -----------------------------------------------------------------------

class TestSimplifyEndpointValidation:
    """Tests for input validation on /engine/simplify."""

    def test_empty_file(self):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("empty.svg", b"", "image/svg+xml")},
            data={"epsilon": "1.0"},
        )
        assert resp.status_code == 422

    def test_not_svg(self):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.txt", b"hello world", "text/plain")},
            data={"epsilon": "1.0"},
        )
        assert resp.status_code == 422

    def test_invalid_output_format(self, test_svg):
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
            data={"epsilon": "1.0", "output_format": "pdf"},
        )
        assert resp.status_code == 422

    def test_default_epsilon(self, test_svg):
        """Epsilon defaults to 1.0 when not specified."""
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", test_svg, "image/svg+xml")},
        )
        assert resp.status_code == 200
        assert resp.json()["format"] == "svg"

    def test_binary_file_rejected(self):
        """Binary (non-UTF-8) file should be rejected."""
        resp = client.post(
            "/engine/simplify",
            files={"file": ("test.svg", b"\x80\x81\x82\xff", "image/svg+xml")},
            data={"epsilon": "1.0"},
        )
        assert resp.status_code == 422
