"""测试活动打印板成员（Active Plate Member）的 3MF 输出行为。

验证内容：
- write_output_3mf 正确保留配置中指定的打印板 G-code 成员
- ZIP 压缩级别参数生效并正确裁剪
- 预览图拼合时仅使用前 9 个唯一输入文件
"""
from __future__ import annotations

import hashlib
import tempfile
import unittest
import zipfile
from pathlib import Path

from a1_swap_mod_packer.builder import (
    normalized_zip_compress_level,
    preview_members_for_gcode_member,
    resolve_output_gcode_member,
    select_preview_sources,
    write_output_3mf,
)
from a1_swap_mod_packer.metadata import read_model_settings_gcode_members
from a1_swap_mod_packer.models import BuildOptions, PlateSource

try:
    from PIL import Image
except Exception:  # pragma: no cover
    Image = None


class ActivePlateMemberTest(unittest.TestCase):
    """测试写入输出 3MF 时活动打印板成员的正确保留与压缩行为。"""

    def test_write_output_preserves_configured_plate_member(self) -> None:
        """验证 write_output_3mf 保留配置中指定打印板对应的 G-code 成员。"""
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source_3mf = root / "plate2.3mf"
            output_3mf = root / "out.3mf"
            self.write_plate2_archive(source_3mf)  # 创建含 plate_2 的测试 3MF

            # 打开源归档，校验 plate_2 被正确识别
            with zipfile.ZipFile(source_3mf, "r") as archive:
                self.assertEqual(read_model_settings_gcode_members(archive), ["Metadata/plate_2.gcode"])
                self.assertEqual(resolve_output_gcode_member(archive, "Metadata/plate_2.gcode"), "Metadata/plate_2.gcode")
                self.assertEqual(
                    preview_members_for_gcode_member("Metadata/plate_2.gcode"),
                    {"Metadata/plate_2.png", "Metadata/plate_2_small.png"},
                )

            gcode_bytes = b"G1 X1\n"  # 替换用的 G-code 内容
            sources = [
                PlateSource(
                    source_3mf=source_3mf,
                    member_name="Metadata/plate_2.gcode",
                    gcode_text="G1 X0\n",
                )
            ]
            options = BuildOptions(
                swap_gcode="",
                output_3mf=output_3mf,
                add_preview_label=False,
                apply_gcode_patches=False,
            )
            md5 = write_output_3mf(source_3mf, output_3mf, gcode_bytes, sources, options)  # 执行打包输出

            # 校验输出归档中仅包含 plate_2 的 G-code，且 MD5 正确
            with zipfile.ZipFile(output_3mf, "r") as archive:
                names = set(archive.namelist())
                self.assertIn("Metadata/plate_2.gcode", names)
                self.assertIn("Metadata/plate_2.gcode.md5", names)
                self.assertNotIn("Metadata/plate_1.gcode", names)
                self.assertEqual(archive.read("Metadata/plate_2.gcode"), gcode_bytes)
                self.assertEqual(archive.read("Metadata/plate_2.gcode.md5").decode(), md5)
                self.assertEqual(md5, hashlib.md5(gcode_bytes).hexdigest())

    def test_write_output_honors_zip_compression_level(self) -> None:
        """验证 ZIP 压缩级别参数生效：压缩级别越高，压缩后文件越小。"""
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source_3mf = root / "plate2.3mf"
            level_1_output = root / "level1.3mf"
            level_7_output = root / "level7.3mf"
            self.write_plate2_archive(source_3mf)
            sources = [
                PlateSource(
                    source_3mf=source_3mf,
                    member_name="Metadata/plate_2.gcode",
                    gcode_text="G1 X0\n",
                )
            ]
            # 大量重复 G-code 以便观察压缩效果
            gcode_bytes = (b"G1 X123.456 Y789.012 E0.03456 ; repeated movement\n" * 20000)

            # 分别以压缩级别 1 和 7 输出
            for output_3mf, zip_level in ((level_1_output, 1), (level_7_output, 7)):
                options = BuildOptions(
                    swap_gcode="",
                    output_3mf=output_3mf,
                    add_preview_label=False,
                    apply_gcode_patches=False,
                    zip_compress_level=zip_level,
                )
                write_output_3mf(source_3mf, output_3mf, gcode_bytes, sources, options)

            # 比较两个输出文件的压缩后大小：级别 7 应更小
            with zipfile.ZipFile(level_1_output, "r") as level_1_archive, zipfile.ZipFile(level_7_output, "r") as level_7_archive:
                level_1_info = level_1_archive.getinfo("Metadata/plate_2.gcode")
                level_7_info = level_7_archive.getinfo("Metadata/plate_2.gcode")
                self.assertEqual(level_1_info.compress_type, zipfile.ZIP_DEFLATED)
                self.assertEqual(level_7_info.compress_type, zipfile.ZIP_DEFLATED)
                self.assertLess(level_7_info.compress_size, level_1_info.compress_size)

    def test_zip_compression_level_is_clamped(self) -> None:
        """验证压缩级别参数被正确裁剪到合法范围 [1, 9]。"""
        self.assertEqual(normalized_zip_compress_level(None), 7)  # 默认值 → 7
        self.assertEqual(normalized_zip_compress_level(0), 1)  # 低于下限 → 1
        self.assertEqual(normalized_zip_compress_level(10), 9)  # 超出上限 → 9

    def test_preview_composite_uses_first_nine_unique_input_files(self) -> None:
        """验证预览图拼合仅使用前 9 个不重复的输入文件，第 10 个被排除。"""
        if Image is None:
            self.skipTest("Pillow is not installed")

        # 10 种不同颜色，用于验证拼合结果
        colors = [
            (255, 0, 0, 255),
            (0, 0, 255, 255),
            (255, 255, 0, 255),
            (255, 0, 255, 255),
            (0, 255, 255, 255),
            (128, 0, 0, 255),
            (0, 0, 128, 255),
            (128, 128, 0, 255),
            (128, 0, 128, 255),
            (255, 128, 0, 255),
        ]

        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            sources: list[PlateSource] = []
            # 为每种颜色创建一个独立的 3MF 源文件
            for index, color in enumerate(colors):
                source_3mf = root / f"source_{index}.3mf"
                self.write_plate2_archive(source_3mf, image_color=color, image_size=300)
                sources.append(
                    PlateSource(
                        source_3mf=source_3mf,
                        member_name="Metadata/plate_2.gcode",
                        gcode_text="G1 X0\n",
                    )
                )

            # 验证 select_preview_sources 返回恰好 9 个源
            selected = select_preview_sources(sources)
            self.assertEqual(len(selected), 9)
            self.assertEqual(selected[-1].source_3mf.name, "source_8.3mf")

            output_3mf = root / "out.3mf"
            options = BuildOptions(
                swap_gcode="",
                output_3mf=output_3mf,
                add_preview_label=True,  # 启用预览标签生成
                apply_gcode_patches=False,
            )
            write_output_3mf(sources[0].source_3mf, output_3mf, b"G1 X1\n", sources, options)

            # 读取拼合后的预览图
            with zipfile.ZipFile(output_3mf, "r") as archive:
                image_bytes = archive.read("Metadata/plate_2.png")

            import io

            # 在拼合图像的 9 个采样点处检测颜色
            image = Image.open(io.BytesIO(image_bytes)).convert("RGBA")
            sampled_colors = {
                image.getpixel((50, 50)),
                image.getpixel((150, 50)),
                image.getpixel((250, 50)),
                image.getpixel((50, 150)),
                image.getpixel((150, 150)),
                image.getpixel((250, 150)),
                image.getpixel((50, 250)),
                image.getpixel((150, 250)),
                image.getpixel((250, 250)),
            }
            # 前 9 种颜色全部出现，第 10 种不出现
            self.assertTrue(set(colors[:9]).issubset(sampled_colors))
            self.assertNotIn(colors[9], sampled_colors)

    @staticmethod
    def write_plate2_archive(path: Path, image_color: tuple[int, int, int, int] | None = None, image_size: int = 128) -> None:
        """创建仅含 plate_2 有效 G-code 成员及其预览图的 3MF 测试归档。

        Args:
            path: 输出 3MF 文件路径
            image_color: 预览图填充颜色（RGBA），None 则写入占位字节
            image_size: 预览图尺寸（正方形）
        """
        model_settings = """<?xml version="1.0" encoding="UTF-8"?>
<config>
  <plate>
    <metadata key="plater_id" value="1"/>
    <metadata key="gcode_file" value=""/>
  </plate>
  <plate>
    <metadata key="plater_id" value="2"/>
    <metadata key="gcode_file" value="Metadata/plate_2.gcode"/>
  </plate>
</config>
"""
        slice_info = """<?xml version="1.0" encoding="UTF-8"?>
<config>
  <plate>
    <metadata key="index" value="2"/>
  </plate>
</config>
"""
        with zipfile.ZipFile(path, "w") as archive:
            archive.writestr("Metadata/model_settings.config", model_settings)
            archive.writestr("Metadata/slice_info.config", slice_info)
            archive.writestr("Metadata/plate_2.gcode", "G1 X0\n")
            archive.writestr("Metadata/plate_2.gcode.md5", "old")
            if image_color is None or Image is None:
                # 无 Pillow 或不指定颜色 → 写入占位字节
                archive.writestr("Metadata/plate_2.png", b"png")
                archive.writestr("Metadata/plate_2_small.png", b"png")
            else:
                import io

                # 生成纯色 PNG 预览图及其缩略图
                image = Image.new("RGBA", (image_size, image_size), image_color)
                output = io.BytesIO()
                image.save(output, format="PNG")
                archive.writestr("Metadata/plate_2.png", output.getvalue())
                small = image.resize((max(1, image_size // 2), max(1, image_size // 2)))
                output = io.BytesIO()
                small.save(output, format="PNG")
                archive.writestr("Metadata/plate_2_small.png", output.getvalue())


if __name__ == "__main__":
    unittest.main()
