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

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/docker-images"

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

    def get_proxy_address(self) -> str:
        return f"{self.vulnerabilities_base_url}/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()
        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.vulnerabilities_base_url}/twistcli/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 asyncio
    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,
            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", f"{self.vulnerabilities_base_url}/report", 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_base_url}/report, 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=f"{self.vulnerabilities_base_url}/report", 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
