"""Unit tests for build/command.py."""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any, Dict, List

import pytest

from byteb4rb1e.sphinxcontrib.h5p.utils.pkg.build.command import build


def _write_pyproject(
    tmp_path: Path,
    machine_name: str,
    vendor_map: Dict[str, str],
) -> None:
    """Write a minimal pyproject.toml with [tool.h5p]."""
    lines = [
        '[project]',
        'name = "test"',
        '',
        '[tool.h5p]',
        'machine_name = "%s"' % machine_name,
        '',
        '[tool.h5p.vendor]',
    ]
    for name, path in vendor_map.items():
        lines.append('"%s" = "%s"' % (name, path))
    (tmp_path / "pyproject.toml").write_text(
        "\n".join(lines) + "\n", encoding="utf-8",
    )


def _write_library_json(
    vendor_dir: Path,
    data: Dict[str, Any],
) -> None:
    """Write library.json to vendor directory."""
    vendor_dir.mkdir(parents=True, exist_ok=True)
    (vendor_dir / "library.json").write_text(
        json.dumps(data, indent=2), encoding="utf-8",
    )


def _write_semantics_json(
    vendor_dir: Path,
    data: List[Dict[str, Any]],
) -> None:
    """Write semantics.json to vendor directory."""
    (vendor_dir / "semantics.json").write_text(
        json.dumps(data, indent=2), encoding="utf-8",
    )


# -------------------------------------------------------------------
# Non-runnable (asset-only) package
# -------------------------------------------------------------------

class TestBuildAssetOnly:
    """Build an asset-only (non-runnable) package."""

    @pytest.fixture
    def pkg_dir(self, tmp_path: Path) -> Path:
        _write_pyproject(
            tmp_path, "FontAwesome",
            {"FontAwesome": "vendor/font-awesome"},
        )
        vendor = tmp_path / "vendor" / "font-awesome"
        _write_library_json(
            vendor,
            {
                "machineName": "FontAwesome",
                "majorVersion": 4,
                "minorVersion": 5,
                "patchVersion": 4,
                "runnable": 0,
                "preloadedCss": [
                    {"path": "css/font-awesome.min.css"},
                ],
            },
        )
        css_dir = vendor / "css"
        css_dir.mkdir()
        (css_dir / "font-awesome.min.css").write_text("body{}")
        return tmp_path

    def test_returns_zero(self, pkg_dir: Path) -> None:
        assert build(pkg_dir) == 0

    def test_generates_init(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/font_awesome"
        init = pkg_dir / "src" / flat / "_generated" / "__init__.py"
        assert init.exists()
        content = init.read_text(encoding="utf-8")
        assert "register_assets" in content
        assert "font-awesome" in content
        assert "font-awesome.min.css" in content

    def test_copies_vendor_assets(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/font_awesome"
        static = (
            pkg_dir / "src" / flat / "_static" / "FontAwesome"
        )
        assert static.is_dir()
        # library.json excluded, css kept
        assert not (static / "library.json").exists()
        assert (static / "css").is_dir()

    def test_no_directive_files(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/font_awesome"
        assert not (
            pkg_dir / "src" / flat / "_generated" / "directive.py"
        ).exists()
        assert not (
            pkg_dir / "src" / flat / "_generated" / "params.py"
        ).exists()


# -------------------------------------------------------------------
# Runnable package
# -------------------------------------------------------------------

_TRUE_FALSE_SEMANTICS: List[Dict[str, Any]] = [
    {
        "name": "question",
        "type": "text",
        "widget": "html",
        "label": "Question",
        "importance": "high",
    },
    {
        "name": "correct",
        "type": "select",
        "label": "Correct answer",
        "importance": "high",
        "options": [
            {"value": "true", "label": "True"},
            {"value": "false", "label": "False"},
        ],
        "default": "true",
    },
    {
        "name": "behaviour",
        "type": "group",
        "label": "Behavioural settings",
        "importance": "low",
        "fields": [
            {
                "name": "enableRetry",
                "type": "boolean",
                "label": "Enable Retry",
                "default": True,
            },
        ],
    },
]


class TestBuildRunnable:
    """Build a runnable package (TrueFalse)."""

    @pytest.fixture
    def pkg_dir(self, tmp_path: Path) -> Path:
        _write_pyproject(
            tmp_path, "H5P.TrueFalse",
            {"H5P.TrueFalse": "vendor/h5p-true-false"},
        )
        vendor = tmp_path / "vendor" / "h5p-true-false"
        _write_library_json(vendor, {
            "machineName": "H5P.TrueFalse",
            "majorVersion": 1,
            "minorVersion": 8,
            "patchVersion": 0,
            "runnable": 1,
            "preloadedJs": [
                {"path": "js/h5p-true-false.js"},
            ],
            "preloadedCss": [
                {"path": "css/h5p-true-false.css"},
            ],
        })
        _write_semantics_json(vendor, _TRUE_FALSE_SEMANTICS)
        js_dir = vendor / "js"
        js_dir.mkdir()
        (js_dir / "h5p-true-false.js").write_text("//js")
        css_dir = vendor / "css"
        css_dir.mkdir()
        (css_dir / "h5p-true-false.css").write_text("/*css*/")
        return tmp_path

    def test_returns_zero(self, pkg_dir: Path) -> None:
        assert build(pkg_dir) == 0

    def test_generates_asset_init(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/true_false"
        init = pkg_dir / "src" / flat / "_generated" / "__init__.py"
        assert init.exists()
        content = init.read_text(encoding="utf-8")
        assert "register_assets" in content

    def test_generates_directive(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/true_false"
        path = (
            pkg_dir / "src" / flat / "_generated" / "directive.py"
        )
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "h5p_true_false" in content
        assert "H5PTrueFalseDirective" in content
        assert "h5p-true-false" in content

    def test_generates_params(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/true_false"
        path = pkg_dir / "src" / flat / "_generated" / "params.py"
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "build_params" in content
        assert "_OPTION_MAP" in content

    def test_copies_vendor_assets(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/true_false"
        static = (
            pkg_dir / "src" / flat / "_static" / "H5P.TrueFalse"
        )
        assert static.is_dir()
        assert not (static / "library.json").exists()
        assert not (static / "semantics.json").exists()

    def test_generates_test_params(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        path = (
            pkg_dir / "tests" / "_generated" / "test_params.py"
        )
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "TestBuildParams" in content

    def test_generates_test_directive(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        path = (
            pkg_dir / "tests" / "_generated" / "test_directive.py"
        )
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "TestDirective" in content

    def test_generates_conftest(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        path = (
            pkg_dir / "tests" / "_generated" / "conftest.py"
        )
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "rootdir" in content

    def test_generates_docs(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        path = (
            pkg_dir / "docs" / "_generated" / "directive.rst"
        )
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "h5p-true-false" in content

    def test_generated_python_is_valid(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/true_false"
        for name in ("__init__.py", "directive.py", "params.py"):
            path = pkg_dir / "src" / flat / "_generated" / name
            content = path.read_text(encoding="utf-8")
            compile(content, str(path), "exec")


# -------------------------------------------------------------------
# Missing library.json
# -------------------------------------------------------------------

class TestBuildMissingLibrary:
    """Vendor directory without library.json."""

    @pytest.fixture
    def pkg_dir(self, tmp_path: Path) -> Path:
        _write_pyproject(
            tmp_path, "H5P.Missing",
            {"H5P.Missing": "vendor/h5p-missing"},
        )
        (tmp_path / "vendor" / "h5p-missing").mkdir(parents=True)
        return tmp_path

    def test_returns_zero(self, pkg_dir: Path) -> None:
        assert build(pkg_dir) == 0

    def test_logs_warning(
        self, pkg_dir: Path, caplog: pytest.LogCaptureFixture,
    ) -> None:
        with caplog.at_level(logging.WARNING):
            build(pkg_dir)
        assert any(
            "No library.json" in r.message for r in caplog.records
        )

    def test_no_generated_files(self, pkg_dir: Path) -> None:
        build(pkg_dir)
        flat = "byteb4rb1e/sphinxcontrib/h5p/missing"
        assert not (
            pkg_dir / "src" / flat / "_generated"
        ).exists()
