"""Tests for Cargo checker cross-referencing Cargo.toml deps against Cargo.lock."""

import os
import tempfile

from pinstack.checkers.cargo import CargoChecker


def _make_index(tmpdir, files):
    # type: (str, dict) -> dict
    """Write files dict {filename: content} into tmpdir and return index."""
    for fname, content in files.items():
        with open(os.path.join(tmpdir, fname), "w") as fh:
            fh.write(content)
    return {tmpdir: set(files.keys())}


CARGO_LOCK_WITH_SERDE = """\
# This file is automatically @generated by Cargo.
version = 3

[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28c57fca5f9df1c44e3b4cd1aaff6c63f1a9f2de0e24c53f6a0e64e8c84e71b"

[[package]]
name = "tokio"
version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
"""

CARGO_LOCK_SERDE_ONLY = """\
# This file is automatically @generated by Cargo.
version = 3

[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28c57fca5f9df1c44e3b4cd1aaff6c63f1a9f2de0e24c53f6a0e64e8c84e71b"
"""


class TestDepInCargoTomlMissingFromLock:
    def setup_method(self):
        self.tmpdir = tempfile.mkdtemp()
        index = _make_index(self.tmpdir, {
            "Cargo.toml": (
                "[package]\n"
                "name = \"myapp\"\n"
                "version = \"0.1.0\"\n"
                "\n"
                "[dependencies]\n"
                "serde = \"1.0\"\n"
                "missing_crate = \"0.5\"\n"
            ),
            "Cargo.lock": CARGO_LOCK_SERDE_ONLY,
        })
        self.findings = CargoChecker().check(index, self.tmpdir)

    def test_dep_in_cargo_toml_missing_from_lock(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        msgs = [f.message for f in cross_ref]
        assert any("missing_crate" in m for m in msgs), (
            "Expected finding for missing_crate, got: {}".format(msgs)
        )

    def test_present_dep_not_flagged(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        msgs = [f.message for f in cross_ref]
        assert not any("serde" in m for m in msgs), (
            "serde should NOT be flagged, got: {}".format(msgs)
        )

    def test_checker_name(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        assert len(cross_ref) >= 1
        assert cross_ref[0].checker == "cargo"

    def test_path_is_relative(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        assert not os.path.isabs(cross_ref[0].path)


class TestDepInCargoTomlPresentInLock:
    def setup_method(self):
        self.tmpdir = tempfile.mkdtemp()
        index = _make_index(self.tmpdir, {
            "Cargo.toml": (
                "[package]\n"
                "name = \"myapp\"\n"
                "version = \"0.1.0\"\n"
                "\n"
                "[dependencies]\n"
                "serde = \"1.0\"\n"
                "tokio = { version = \"1.0\", features = [\"full\"] }\n"
            ),
            "Cargo.lock": CARGO_LOCK_WITH_SERDE,
        })
        self.findings = CargoChecker().check(index, self.tmpdir)

    def test_dep_in_cargo_toml_present_in_lock(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        assert cross_ref == [], (
            "No cross-ref findings expected when all deps are in lock, got: {}".format(
                [f.message for f in cross_ref]
            )
        )


class TestDevDepsChecked:
    def setup_method(self):
        self.tmpdir = tempfile.mkdtemp()
        index = _make_index(self.tmpdir, {
            "Cargo.toml": (
                "[package]\n"
                "name = \"myapp\"\n"
                "version = \"0.1.0\"\n"
                "\n"
                "[dependencies]\n"
                "serde = \"1.0\"\n"
                "\n"
                "[dev-dependencies]\n"
                "criterion = \"0.5\"\n"
            ),
            "Cargo.lock": CARGO_LOCK_SERDE_ONLY,
        })
        self.findings = CargoChecker().check(index, self.tmpdir)

    def test_dev_deps_checked(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        msgs = [f.message for f in cross_ref]
        assert any("criterion" in m for m in msgs), (
            "Expected criterion (dev-dep) to be flagged, got: {}".format(msgs)
        )


class TestNoCargoToml:
    def setup_method(self):
        self.tmpdir = tempfile.mkdtemp()
        # Only Cargo.lock present — no Cargo.toml, so no cross-ref checks
        index = _make_index(self.tmpdir, {
            "Cargo.lock": CARGO_LOCK_WITH_SERDE,
        })
        self.findings = CargoChecker().check(index, self.tmpdir)

    def test_no_cargo_toml(self):
        cross_ref = [f for f in self.findings if "stale" in f.message]
        assert cross_ref == [], (
            "No cross-ref findings expected when Cargo.toml absent, got: {}".format(
                [f.message for f in cross_ref]
            )
        )
