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

import aiohttp
import requests
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_get_headers, get_default_post_headers


class TwistcliIntegration(ABC):
    vulnerabilities_base_url = f"{bc_integration.api_url}/api/v1/vulnerabilities"
    vulnerabilities_save_results_url = f"{vulnerabilities_base_url}/results"
    twictcli_base_url = f"{vulnerabilities_base_url}/docker-images/twistcli"

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

    def get_proxy_address(self) -> str:
        return f"{self.twictcli_base_url}/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()
        headers = merge_dicts(
            get_default_get_headers(bc_integration.bc_source, bc_integration.bc_source_version),
            {"Authorization": self.get_bc_api_key()},
        )
        response = requests.request(
            "GET", f"{self.twictcli_base_url}/download?os={os_type}", headers=headers
        )
        response.raise_for_status()

        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,
        )
        headers = merge_dicts(
            get_default_post_headers(bc_integration.bc_source, bc_integration.bc_source_version),
            {"Authorization": self.get_bc_api_key()},
        )

        for i in range(2):
            response = requests.request("POST", self.vulnerabilities_save_results_url, headers=headers, json=payload)
            try:
                response.raise_for_status()
                break
            except requests.exceptions.HTTPError as ex:
                logging.error(f"HTTP error on request {self.vulnerabilities_save_results_url}, payload:\n{payload}")
                if ex.response.status_code >= 500 and i != 1:
                    time.sleep(2)
                    continue
                raise ex

    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=self.vulnerabilities_save_results_url, 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 []
        ]
