import json
from typing import Any

import pytest

from sbomgrader.core.documents import Document
from sbomgrader.core.formats import SBOMFormat
from sbomgrader.translate.prune import prune
from sbomgrader.translate.translation_map import TranslationMap
from sbomgrader.core.utils import get_mapping


@pytest.fixture(scope="session")
def built_in_translation_map() -> TranslationMap:
    yield TranslationMap.from_file(
        "sbomgrader/translation_maps/red_hat_spdx23_cdx16.yml"
    )


def ordered(obj: Any):
    """Orders lists so translation is not order-dependent."""
    if isinstance(obj, dict):
        return sorted((k, ordered(v)) for k, v in obj.items())
    if isinstance(obj, list):
        return sorted(ordered(x) for x in obj)
    else:
        return obj


class ComparableDoc:
    def __init__(self, doc: Document):
        self.document = doc

    def __eq__(self, other):
        if not isinstance(other, ComparableDoc) or not isinstance(other, Document):
            raise TypeError(
                f"Cannot compare object of type {self.__class__.__name__} to an object  of type {other.__class__.__name__}."
            )


@pytest.mark.parametrize(["spdx"], [("spdx23",), ("spdx22",)])
@pytest.mark.parametrize(["cyclonedx"], [("cdx16",), ("cdx15",)])
def test_translation(cyclonedx, spdx):
    tm = TranslationMap.from_file(
        "tests/testdata/test_translation/sample_spdx23_cdx16.yml"
    )
    first_doc = Document(
        get_mapping(f"tests/testdata/test_translation/sample_{spdx}.json")
    )
    second_doc = Document(
        get_mapping(f"tests/testdata/test_translation/sample_{cyclonedx}.json")
    )
    assert tm.convert(first_doc, SBOMFormat(cyclonedx)).doc == second_doc.doc
    assert tm.convert(second_doc, SBOMFormat(spdx)).doc == first_doc.doc


def test_spdx_product_translation(built_in_translation_map: TranslationMap):
    spdx_doc = Document.from_file(
        "tests/testdata/test_translation/actual_data/rhel-9.2-eus.spdx.json"
    )
    cdx_doc = built_in_translation_map.convert(spdx_doc)
    new_doc = built_in_translation_map.convert(cdx_doc)
    for doc in spdx_doc, new_doc:
        # Ignore these autogenerated fields:
        doc.doc.pop("documentNamespace")
        creators = doc.doc.get("creationInfo").get("creators")
        creators = [c for c in creators if not c.startswith("Tool: SBOMGrader")]
        doc.doc["creationInfo"]["creators"] = creators
    assert ordered(spdx_doc.doc) == ordered(new_doc.doc)


def test_cdx_product_translation(built_in_translation_map: TranslationMap):
    cdx_doc = Document.from_file(
        "tests/testdata/test_translation/actual_data/rhel-9.2-eus.cdx.json"
    )
    new_doc = built_in_translation_map.convert(
        built_in_translation_map.convert(cdx_doc)
    )
    for doc in cdx_doc, new_doc:
        # Ignore these autogenerated fields:
        doc.doc.pop("serialNumber")
        tools = doc.doc.get("metadata").get("tools").get("components", [])
        tools = [t for t in tools if t.get("name") != "SBOMGrader"]
        doc.doc["metadata"]["tools"] = tools
        prune(doc.doc)
    assert ordered(cdx_doc.doc) == ordered(new_doc.doc)


def test_spdx_rpm_translation(built_in_translation_map: TranslationMap):
    spdx_doc = Document.from_file(
        "tests/testdata/test_translation/actual_data/openssl-3.0.7-18.el9_2.spdx.json"
    )
    cdx_doc = built_in_translation_map.convert(spdx_doc)
    new_doc = built_in_translation_map.convert(cdx_doc)
    for doc in spdx_doc, new_doc:
        # Ignore these autogenerated fields:
        doc.doc.pop("documentNamespace")
        doc.doc.pop("files", None)
        creators = doc.doc.get("creationInfo").get("creators")
        creators = [c for c in creators if not c.startswith("Tool: SBOMGrader")]
        doc.doc["creationInfo"]["creators"] = creators
        for component in doc.doc.get("packages", []):
            for annotation in component.get("annotations", []):
                for key in "annotator", "annotationDate":
                    annotation.pop(key, None)
            component.pop("packageFileName", None)
        prune(doc.doc)
    assert ordered(spdx_doc.doc) == ordered(new_doc.doc)


def test_cdx_rpm_translation(built_in_translation_map: TranslationMap):
    cdx_doc = Document.from_file(
        "tests/testdata/test_translation/actual_data/openssl-3.0.7-18.el9_2.cdx.json"
    )
    spdx_doc = built_in_translation_map.convert(cdx_doc)
    new_doc = built_in_translation_map.convert(spdx_doc)
    for doc in cdx_doc, new_doc:
        # Ignore these autogenerated fields:
        doc.doc.pop("serialNumber")
        doc.doc.get("metadata", {}).get("component", {}).pop("pedigree", None)
        tools = doc.doc.get("metadata").get("tools").get("components", [])
        tools = [t for t in tools if t.get("name") != "SBOMGrader"]
        doc.doc["metadata"]["tools"] = tools
        prune(doc.doc)
    assert ordered(cdx_doc.doc) == ordered(new_doc.doc)


def test_cdx_image_translation(built_in_translation_map: TranslationMap):
    cdx_doc = Document.from_file(
        "tests/testdata/test_translation/actual_data/mandrel-for-jdk-21-rhel8-container-23.1-16_amd64.cdx.json"
    )
    spdx_doc = built_in_translation_map.convert(cdx_doc)
    new_doc = built_in_translation_map.convert(spdx_doc)
    for doc in cdx_doc, new_doc:
        # Ignore these autogenerated fields:
        doc.doc.pop("serialNumber")
        doc.doc.get("metadata").pop("supplier", None)
        to_remove = []
        for idx, dep in enumerate(doc.doc.get("dependencies")):
            if not dep.get("dependsOn"):
                to_remove.append(idx)
        for idx in reversed(to_remove):
            doc.doc["dependencies"].pop(idx)
        for component in doc.doc["components"]:
            component.get("supplier", {}).pop("url", None)
        doc.doc.get("metadata").get("component").get("supplier", {}).pop("url", None)
        tools = doc.doc.get("metadata").get("tools", {}).get("components", [])
        tools = [t for t in tools if t.get("name") != "SBOMGrader"]
        doc.doc["metadata"]["tools"] = tools
        prune(doc.doc)
    assert ordered(cdx_doc.doc) == ordered(new_doc.doc)
