#  SPDX-FileCopyrightText: 2023 spdx contributors
#
#  SPDX-License-Identifier: Apache-2.0
import logging
from datetime import datetime
from typing import List

from license_expression import get_spdx_licensing

from spdx_tools.spdx.model import (
    Actor,
    ActorType,
    Checksum,
    ChecksumAlgorithm,
    CreationInfo,
    Document,
    ExternalPackageRef,
    ExternalPackageRefCategory,
    File,
    FileType,
    Package,
    PackagePurpose,
    PackageVerificationCode,
    Relationship,
    RelationshipType,
)
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document
from spdx_tools.spdx.validation.validation_message import ValidationMessage

# This example shows how to use the spdx-tools to create an SPDX document from scratch,
# validate it and write it to a file.


def test_spdx2_document_from_scratch():
    # First up, we need general information about the creation of the document, summarised by the CreationInfo class.
    creation_info = CreationInfo(
        spdx_version="SPDX-2.3",
        spdx_id="SPDXRef-DOCUMENT",
        name="document name",
        data_license="CC0-1.0",
        document_namespace="https://some.namespace",
        creators=[Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com")],
        created=datetime(2022, 1, 1),
    )

    # creation_info is the only required property of the Document class (have a look there!),
    # the rest are optional lists.
    # So, we are set up to create a new document instance.
    document = Document(creation_info)

    # The document currently does not describe anything. Let's create a package that we can add to it.
    # The Package class has quite a few properties (have a look there!),
    # but only name, spdx_id and download_location are mandatory in SPDX v2.3.
    package = Package(
        name="package name",
        spdx_id="SPDXRef-Package",
        download_location="https://download.com",
        version="2.2.1",
        file_name="./foo.bar",
        supplier=Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com"),
        originator=Actor(ActorType.ORGANIZATION, "some organization", "contact@example.com"),
        files_analyzed=True,
        verification_code=PackageVerificationCode(
            value="d6a770ba38583ed4bb4525bd96e50461655d2758", excluded_files=["./some.file"]
        ),
        checksums=[
            Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"),
            Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"),
        ],
        license_concluded=get_spdx_licensing().parse("GPL-2.0-only OR MIT"),
        license_info_from_files=[get_spdx_licensing().parse("GPL-2.0-only"), get_spdx_licensing().parse("MIT")],
        license_declared=get_spdx_licensing().parse("GPL-2.0-only AND MIT"),
        license_comment="license comment",
        copyright_text="Copyright 2022 Jane Doe",
        description="package description",
        attribution_texts=["package attribution"],
        primary_package_purpose=PackagePurpose.LIBRARY,
        release_date=datetime(2015, 1, 1),
        external_references=[
            ExternalPackageRef(
                category=ExternalPackageRefCategory.OTHER,
                reference_type="http://reference.type",
                locator="reference/locator",
                comment="external reference comment",
            )
        ],
    )

    # Now that we have a package defined, we can add it to the document's package property.
    document.packages = [package]

    # A DESCRIBES relationship asserts that the document indeed describes the package.
    describes_relationship = Relationship("SPDXRef-DOCUMENT", RelationshipType.DESCRIBES, "SPDXRef-Package")
    document.relationships = [describes_relationship]

    # Let's add two files. Have a look at the file class for all possible properties a file can have.
    file1 = File(
        name="./package/file1.py",
        spdx_id="SPDXRef-File1",
        file_types=[FileType.SOURCE],
        checksums=[
            Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"),
            Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"),
        ],
        license_concluded=get_spdx_licensing().parse("MIT"),
        license_info_in_file=[get_spdx_licensing().parse("MIT")],
        copyright_text="Copyright 2022 Jane Doe",
    )
    file2 = File(
        name="./package/file2.py",
        spdx_id="SPDXRef-File2",
        checksums=[
            Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2759"),
        ],
        license_concluded=get_spdx_licensing().parse("GPL-2.0-only"),
    )

    # Assuming the package contains those two files, we create two CONTAINS relationships.
    contains_relationship1 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1")
    contains_relationship2 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File2")

    # This library uses run-time type checks when assigning properties.
    # Because in-place alterations like .append() circumvent these checks, we don't use them here.
    document.relationships += [contains_relationship1, contains_relationship2]
    document.files += [file1, file2]

    # We now have created a document with basic creation information, describing a package that contains two files.
    # You can also add Annotations, Snippets and ExtractedLicensingInfo
    # to the document in an analogous manner to the above.
    # Have a look at their respective classes if you are unsure about their properties.
    assert len(document.packages) == 1
    assert len(document.files) == 2
    assert len(document.relationships) == 3
    assert len(document.snippets) == 0
    assert len(document.annotations) == 0
    assert len(document.extracted_licensing_info) == 0

    # This library provides comprehensive validation against the SPDX specification.
    # Note that details of the validation depend on the SPDX version of the document.
    validation_messages: List[ValidationMessage] = validate_full_spdx_document(document)

    # You can have a look at each entry's message and context (like spdx_id, parent_id, full_element)
    # which will help you pinpoint the location of the invalidity.
    for message in validation_messages:
        logging.warning(message.validation_message)
        logging.warning(message.context)

    # If the document is valid, validation_messages will be empty.
    assert validation_messages == []
