from __future__ import annotations

import logging
import platform
import stat
from abc import abstractmethod, ABC
from pathlib import Path
from typing import Dict, Any, List, TYPE_CHECKING
from datetime import datetime, timedelta

from checkov.common.bridgecrew.platform_integration import bc_integration
from checkov.common.util.data_structures_utils import merge_dicts
from checkov.common.util.http_utils import get_default_post_headers, request_wrapper, aiohttp_client_session_wrapper

if TYPE_CHECKING:
    from checkov.common.bridgecrew.platform_integration import BcPlatformIntegration


class TwistcliIntegration(ABC):
    vulnerabilities_base_path = "/api/v1/vulnerabilities"  # noqa: CCE003  # a static attribute

    def get_bc_api_key(self) -> str:
        return bc_integration.get_auth_token()

    def get_proxy_address(self) -> str:
        return f"{bc_integration.api_url}{self.vulnerabilities_base_path}/docker-images/twistcli/proxy"

    def download_twistcli(self, cli_file_name: Path) -> bool:
        # backwards compatibility, should be removed in a later stage
        try:
            cli_file_name_path = cli_file_name if isinstance(cli_file_name, Path) else Path(cli_file_name)
            os_type = platform.system().lower()
            response = request_wrapper("GET",
                                       f"{bc_integration.api_url}{self.vulnerabilities_base_path}/twistcli?os={os_type}",
                                       headers=bc_integration.get_default_headers("GET"),
                                       should_call_raise_for_status=True)

            cli_file_name_path.write_bytes(response.content)
            cli_file_name_path.chmod(cli_file_name_path.stat().st_mode | stat.S_IEXEC)
            logging.debug("twistcli downloaded and has execute permission")
            return True
        except Exception:
            logging.debug(
                "Unexpected failure happened during downloading twistcli. details are below.\n"
                "scanning is terminating. please try again. if it is repeated, please report.\n", exc_info=True)
            return False

    async def report_results_async(
        self,
        twistcli_scan_result: Dict[str, Any],
        bc_platform_integration: BcPlatformIntegration,
        bc_api_key: str,
        file_path: Path,
        **kwargs: Any,
    ) -> int:
        logging.info(f"Start to send report for package file {file_path}")

        if not bc_platform_integration.bc_source:
            logging.error("Source was not set")
            return 1

        payload = self.create_report(
            twistcli_scan_result=twistcli_scan_result,
            bc_platform_integration=bc_platform_integration,
            file_path=file_path,
            **kwargs,
        )
        headers = merge_dicts(
            get_default_post_headers(bc_platform_integration.bc_source, bc_platform_integration.bc_source_version),
            {"Authorization": bc_api_key},
        )
        url = f"{bc_platform_integration.api_url}{self.vulnerabilities_base_path}/results"

        logging.info(f"[twistcli](report_results_async) - reporting results to the server for the file \'{file_path}\'")
        status: int = await aiohttp_client_session_wrapper(url, headers, payload)

        if status == 1:
            logging.error(f"[twistcli](report_results_async) - Failed to send report for package file {file_path}"
                          f"\nerror message appears above")
        return status

    @abstractmethod
    def create_report(
        self,
        twistcli_scan_result: dict[str, Any],
        bc_platform_integration: BcPlatformIntegration,
        file_path: Path,
        **kwargs: Any,
    ) -> dict[str, Any]:
        pass

    @staticmethod
    def get_vulnerabilities_for_report(scan_results: Dict[str, Any]) -> List[Dict[str, Any]]:
        return [
            {
                "cveId": vul.get("id"),
                "status": vul.get("status", "open"),
                "severity": vul.get("severity"),
                "packageName": vul.get("packageName"),
                "packageVersion": vul.get("packageVersion"),
                "link": vul.get("link"),
                "cvss": vul.get("cvss"),
                "vector": vul.get("vector"),
                "description": vul.get("description"),
                "riskFactors": vul.get("riskFactors"),
                "publishedDate": vul.get("publishedDate") or
                                 (datetime.now() - timedelta(days=vul.get("publishedDays", 0))).isoformat(),
            }
            for vul in scan_results.get("vulnerabilities") or []
        ]

    @staticmethod
    def get_packages_for_report(scan_results: Dict[str, Any]) -> List[Dict[str, Any]]:
        return [
            {
                "type": package.get("type"),
                "name": package.get("name"),
                "version": package.get("version"),
                "licenses": package.get("licenses") or [],
            }
            for package in scan_results.get("packages") or []
            if package.get("version") and package.get("name") and package.get("type")
        ]
