from __future__ import annotations
import json
import logging
import os
from subprocess import CalledProcessError

from ossprey.environment import get_environment_details
from ossprey.exceptions import (
    InvalidScanModeException,
    MissingPackageException,
    NoPackageManagerException,
    ScanFailedException,
)
from ossprey.modes import get_modes, get_all_modes
from ossprey.sbom_python import (
    update_sbom_from_requirements,
    update_sbom_from_poetry,
    update_sbom_from_virtualenv,
)
from ossprey.sbom_javascript import update_sbom_from_npm, update_sbom_from_yarn
from ossprey.sbom_filesystem import update_sbom_from_filesystem
from ossprey.ossprey import Ossprey

from ossbom.converters.factory import SBOMConverterFactory
from ossbom.model.ossbom import OSSBOM
from ossbom.model.vulnerability import Vulnerability

logger = logging.getLogger(__name__)


def scan_python(modes: list[str], sbom: OSSBOM, package_name: str) -> OSSBOM:

    if "python-requirements" in modes:
        sbom = update_sbom_from_requirements(sbom, package_name + "/requirements.txt")

    if "poetry" in modes:
        try:
            sbom = update_sbom_from_poetry(sbom, package_name)
        except CalledProcessError as e:
            logger.error(
                "Error running poetry scan, skipping poetry scan, attempting pipenv scan instead"
            )
            logger.debug(e.stderr)
            logger.debug("--")
            logger.debug(e.stdout)

            # Appending pipenv to modes to attempt to get some python dependencies if poetry fails
            modes.append("pipenv")

    if "pipenv" in modes:
        sbom = update_sbom_from_virtualenv(sbom, package_name)

    return sbom


def scan_javascript(modes: list[str], sbom: OSSBOM, package_name: str) -> OSSBOM:
    if "npm" in modes:
        sbom = update_sbom_from_npm(sbom, package_name)

    if "yarn" in modes:
        sbom = update_sbom_from_yarn(sbom, package_name)

    return sbom


def scan_filesystem(modes: list[str], sbom: OSSBOM, package_name: str) -> OSSBOM:

    if "fs" in modes:
        sbom = update_sbom_from_filesystem(sbom, package_name)

    return sbom


def scan(
    package_name: str,
    mode: str = "auto",
    local_scan: str | None = None,
    url: str | None = None,
    api_key: str | None = None,
) -> OSSBOM:

    if mode == "auto":
        logger.debug("Auto mode selected")

        # Check the folder for files that map to different package managers
        modes = get_modes(package_name)
        if len(modes) == 0:
            logger.error("No package manager found")
            raise NoPackageManagerException("No package manager found in the directory")
    else:
        modes = [mode]

    logger.info(f"Scanning {package_name} with modes: {modes}")
    # If package location doesn't exist, raise an error
    if not os.path.exists(package_name):
        logger.error(f"Package {package_name} does not exist")
        raise MissingPackageException(f"Package {package_name} does not exist")

    sbom = OSSBOM()

    if any(mode not in get_all_modes() for mode in modes) or len(modes) == 0:
        raise InvalidScanModeException("Invalid scanning method: " + str(modes))

    # Enrich the sbom based on the scans provided
    sbom = scan_python(modes, sbom, package_name)
    sbom = scan_javascript(modes, sbom, package_name)
    sbom = scan_filesystem(modes, sbom, package_name)

    # Update sbom to contain the local environment
    env = get_environment_details(package_name)
    sbom.update_environment(env)

    logger.info(f"Scanning {len(sbom.get_components())} components")

    if not local_scan:
        ossprey = Ossprey(url, api_key)

        # Compress to MINIBOM
        sbom = SBOMConverterFactory.to_minibom(sbom)

        sbom = ossprey.validate(sbom)
        if not sbom:
            raise ScanFailedException("Issue OSSPREY Service")

        # Convert to OSSBOM
        sbom = SBOMConverterFactory.from_minibom(sbom)

    if local_scan == "dry-run-malicious":
        # Add a vulnerability for testing purposes
        components = sbom.get_components()
        if len(components) == 0:
            raise Exception("No components found to add a test vulnerability")

        component = sbom.get_components()[0]
        purl = f"pkg:{component.type}/{component.name}@{component.version}"
        vulnerability = Vulnerability(
            id="TEST-2024-0001",
            purl=purl,
            description="This is a test vulnerability added in dry-run-malicious mode",
        )
        sbom.add_vulnerability(vulnerability)

    logger.debug(json.dumps(sbom.to_dict(), indent=4))

    return sbom
