from __future__ import annotations

from pathlib import Path

import pytest

from rimpack.core.modpack import (
    PackInfo,
    PackLayout,
    PackPaths,
    PackTomlCommentLine,
    PackTomlKeyValueLine,
    PackTomlError,
    PackTomlManifest,
    PackTomlTableLine,
    load_pack_toml,
    new_pack_toml_manifest,
    parse_pack_toml,
    render_pack_toml,
    with_added_mod_file,
    with_added_ordering_rule_file,
)


def test_parse_pack_toml_with_current_supported_fields() -> None:
    manifest = parse_pack_toml(
        """\
[pack]
name = "My Modpack"
rimworld_version = "1.5"
pack_format_version = 1
author = "Natalia"
description = "Large themed pack focused on combat."

[layout]
mod_files = [
  "mods/00_core.list",
  "mods/10_libraries.list",
]

[paths]
config_dir = "config/Mod_Configs"
ordering_rule_files = [
  "rules/ordering/30_combat.toml",
]
activation_rule_files = [
  "rules/activation/40_patches.toml",
]
diagnostics_file = "generated/diagnostics.json"
resolved_load_order_file = "generated/resolved_load_order.txt"
local_mods_dir = "local/mods"
""",
    )

    assert manifest == PackTomlManifest(
        pack=PackInfo(
            name="My Modpack",
            rimworld_version="1.5",
            pack_format_version=1,
            author="Natalia",
            description="Large themed pack focused on combat.",
        ),
        layout=PackLayout(
            mod_files=(
                "mods/00_core.list",
                "mods/10_libraries.list",
            ),
        ),
        paths=PackPaths(
            config_dir="config/Mod_Configs",
            ordering_rule_files=("rules/ordering/30_combat.toml",),
            activation_rule_files=("rules/activation/40_patches.toml",),
            diagnostics_file="generated/diagnostics.json",
            resolved_load_order_file="generated/resolved_load_order.txt",
            local_mods_dir="local/mods",
        ),
    )
    assert manifest.source_text.startswith("[pack]\nname = \"My Modpack\"")
    assert isinstance(manifest.lines[0], PackTomlTableLine)
    assert manifest.lines[0] == PackTomlTableLine(
        raw_text="[pack]",
        name="pack",
        line_ending="\n",
    )


def test_parse_pack_toml_with_required_sections_only() -> None:
    manifest = parse_pack_toml(
        """\
[pack]
name = "Minimal Pack"
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = ["mods/00_core.list"]
""",
    )

    assert manifest == PackTomlManifest(
        pack=PackInfo(
            name="Minimal Pack",
            rimworld_version="1.6",
            pack_format_version=1,
        ),
        layout=PackLayout(mod_files=("mods/00_core.list",)),
        paths=PackPaths(),
    )


def test_load_pack_toml_from_path() -> None:
    path = Path("tests") / "__tmp_pack.toml"
    try:
        _ = path.write_text(
            """\
[pack]
name = "Disk Pack"
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = ["mods/00_core.list"]
""",
            encoding="utf-8",
        )

        manifest = load_pack_toml(path)

        assert manifest.pack.name == "Disk Pack"
        assert manifest.layout.mod_files == ("mods/00_core.list",)
    finally:
        path.unlink(missing_ok=True)


def test_reject_missing_required_fields() -> None:
    with pytest.raises(PackTomlError, match=r"missing required field \[pack\]\.name"):
        _ = parse_pack_toml(
            """\
[pack]
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = ["mods/00_core.list"]
""",
        )


def test_reject_invalid_or_unsupported_shapes() -> None:
    with pytest.raises(PackTomlError, match=r"unsupported \[pack\]\.pack_format_version"):
        _ = parse_pack_toml(
            """\
[pack]
name = "Pack"
rimworld_version = "1.6"
pack_format_version = 2

[layout]
mod_files = ["mods/00_core.list"]
""",
        )


def test_reject_invalid_path_shapes_and_duplicates() -> None:
    with pytest.raises(PackTomlError, match=r"must use forward slashes"):
        _ = parse_pack_toml(
            """\
[pack]
name = "Pack"
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = ["mods\\\\00_core.list"]
""",
        )

    with pytest.raises(PackTomlError, match=r"must not contain duplicates"):
        _ = parse_pack_toml(
            """\
[pack]
name = "Pack"
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = ["mods/00_core.list", "mods/00_core.list"]
""",
        )


def test_preserve_comments_unknown_sections_and_raw_assignment_blocks() -> None:
    manifest = parse_pack_toml(
        """\
# pack header
[pack]
name = "Pack"
rimworld_version = "1.6"
pack_format_version = 1
custom_flag = true

[layout]
mod_files = [
  "mods/00_core.list",
]

[future]
value = "keep me"
""",
    )

    assert manifest.pack.name == "Pack"
    assert manifest.lines[0] == PackTomlCommentLine(
        raw_text="# pack header",
        line_ending="\n",
    )
    custom_flag_line = next(
        line
        for line in manifest.lines
        if isinstance(line, PackTomlKeyValueLine) and line.key == "custom_flag"
    )
    assert custom_flag_line == PackTomlKeyValueLine(
        raw_lines=('custom_flag = true',),
        line_endings=("\n",),
        table_name="pack",
        key="custom_flag",
        value=True,
    )
    layout_table_line = next(
        line
        for line in manifest.lines
        if isinstance(line, PackTomlTableLine) and line.name == "layout"
    )
    assert layout_table_line == PackTomlTableLine(
        raw_text="[layout]",
        name="layout",
        line_ending="\n",
    )
    mod_files_line = next(
        line
        for line in manifest.lines
        if isinstance(line, PackTomlKeyValueLine) and line.key == "mod_files"
    )
    assert mod_files_line == PackTomlKeyValueLine(
        raw_lines=(
            "mod_files = [",
            '  "mods/00_core.list",',
            "]",
        ),
        line_endings=("\n", "\n", "\n"),
        table_name="layout",
        key="mod_files",
        value=["mods/00_core.list"],
    )


def test_render_pack_toml_round_trips_parsed_manifest() -> None:
    source = (
        "# pack header\n"
        "[pack]\n"
        'name = "Pack"\n'
        'rimworld_version = "1.6"\n'
        "pack_format_version = 1\n"
        "\n"
        "[layout]\n"
        'mod_files = ["mods/00_core.list"]\n'
    )

    manifest = parse_pack_toml(source)

    assert render_pack_toml(manifest) == source


def test_render_new_pack_toml_manifest_uses_canonical_supported_format() -> None:
    manifest = new_pack_toml_manifest(
        pack=PackInfo(
            name="My Pack",
            rimworld_version="1.6",
            pack_format_version=1,
        ),
        layout=PackLayout(
            mod_files=(
                "mods/00_core.list",
                "mods/10_libraries.list",
            ),
        ),
        paths=PackPaths(
            config_dir="config/Mod_Configs",
            diagnostics_file="generated/diagnostics.json",
        ),
    )

    assert render_pack_toml(manifest) == (
        "[pack]\n"
        'name = "My Pack"\n'
        'rimworld_version = "1.6"\n'
        "pack_format_version = 1\n"
        "\n"
        "[layout]\n"
        "mod_files = [\n"
        '  "mods/00_core.list",\n'
        '  "mods/10_libraries.list",\n'
        "]\n"
        "\n"
        "[paths]\n"
        'config_dir = "config/Mod_Configs"\n'
        'diagnostics_file = "generated/diagnostics.json"\n'
    )


def test_with_added_ordering_rule_file_preserves_existing_manifest_content() -> None:
    manifest = parse_pack_toml(
        """\
# pack header
[pack]
name = "Pack"
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = ["mods/00_core.list"]

[paths]
# keep this comment
config_dir = "config/Mod_Configs"
diagnostics_file = "generated/diagnostics.json"
""",
    )

    updated_manifest = with_added_ordering_rule_file(
        manifest,
        relative_path="rules/ordering/30_combat.toml",
    )

    assert updated_manifest.paths.ordering_rule_files == ("rules/ordering/30_combat.toml",)
    assert render_pack_toml(updated_manifest) == (
        "# pack header\n"
        "[pack]\n"
        'name = "Pack"\n'
        'rimworld_version = "1.6"\n'
        "pack_format_version = 1\n"
        "\n"
        "[layout]\n"
        'mod_files = ["mods/00_core.list"]\n'
        "\n"
        "[paths]\n"
        "# keep this comment\n"
        'config_dir = "config/Mod_Configs"\n'
        'diagnostics_file = "generated/diagnostics.json"\n'
        "ordering_rule_files = [\n"
        '  "rules/ordering/30_combat.toml",\n'
        "]\n"
    )


def test_with_added_mod_file_preserves_existing_manifest_content() -> None:
    manifest = parse_pack_toml(
        """\
# pack header
[pack]
name = "Pack"
rimworld_version = "1.6"
pack_format_version = 1

[layout]
mod_files = [
  "mods/00_core.list",
]

[paths]
config_dir = "config/Mod_Configs"
""",
    )

    updated_manifest = with_added_mod_file(
        manifest,
        relative_path="mods/10_libraries.list",
    )

    assert updated_manifest.layout.mod_files == (
        "mods/00_core.list",
        "mods/10_libraries.list",
    )
    assert render_pack_toml(updated_manifest) == (
        "# pack header\n"
        "[pack]\n"
        'name = "Pack"\n'
        'rimworld_version = "1.6"\n'
        "pack_format_version = 1\n"
        "\n"
        "[layout]\n"
        "mod_files = [\n"
        '  "mods/00_core.list",\n'
        '  "mods/10_libraries.list",\n'
        "]\n"
        "\n"
        "[paths]\n"
        'config_dir = "config/Mod_Configs"\n'
    )
