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

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


class TwistcliIntegration(ABC):
    vulnerabilities_base_path = "/api/v1/vulnerabilities"

    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) -> None:
        # backwards compatibility, should be removed in a later stage
        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")

    # can be removed, if image scanning is also using report_results_asyncio
    # make sure to remove it only after validating it isn't used from the platform
    def report_results(self, twistcli_scan_result: Dict[str, Any], file_path: Path, **kwargs: Any) -> None:
        payload = self.create_report(
            twistcli_scan_result=twistcli_scan_result,
            bc_platform_integration=bc_integration,
            file_path=file_path,
            **kwargs,
        )

        request_wrapper("POST", f"{bc_integration.api_url}{self.vulnerabilities_base_path}/results",
                        headers=bc_integration.get_default_headers("POST"),
                        json=payload, should_call_raise_for_status=True)

    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}")

        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},
        )

        async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(resolver=aiohttp.AsyncResolver())) as session:
            async with session.post(
                url=f'{bc_integration.api_url}{self.vulnerabilities_base_path}/results', headers=headers, json=payload
            ) as response:
                content = await response.text()

            if response.ok:
                logging.info(f"Successfully send report for package file {file_path}")
                return 0
            else:
                logging.error(f"Failed to send report for package file {file_path}")
                logging.error(f"Status code: {response.status}, Reason: {response.reason}, Content: {content}")
                return 1

    @abstractmethod
    def create_report(self, **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 []
        ]