import logging
import subprocess  # nosec
from typing import Union, Dict, Any

import docker
import json
import os
import time

from checkov.common.bridgecrew.vulnerability_scanning.integrations.docker_image_scanning import docker_image_scanning_integration

TWISTCLI_FILE_NAME = 'twistcli'
DOCKER_IMAGE_SCAN_RESULT_FILE_NAME = 'docker-image-scan-results.json'


def _get_docker_image_name(docker_image_id: str) -> str:
    try:
        docker_client = docker.from_env()
        return docker_client.images.get(docker_image_id).attrs['RepoDigests'][0].split('@')[0]
    except Exception as e:
        logging.error("docker image needs to have repository")
        raise e


def _get_dockerfile_content(dockerfile_path: Union[str, "os.PathLike[str]"]) -> str:
    try:
        with open(dockerfile_path) as f:
            return f.read()
    except FileNotFoundError as e:
        logging.error(f"Path to Dockerfile is invalid\n{e}")
        raise e
    except Exception as e:
        logging.error(f"Failed to read Dockerfile content\n{e}")
        raise e


class ImageScanner:
    def __init__(self) -> None:
        self.docker_image_name = ''
        self.dockerfile_content = ''

    def setup_scan(
        self,
        docker_image_id: str,
        dockerfile_path: Union[str, "os.PathLike[str]"],
        skip_extract_image_name: bool,
    ) -> None:
        try:
            if skip_extract_image_name:
                # Provide a default image name in case the image has not been tagged with a name
                self.docker_image_name = f'repository/image{str(time.time() * 1000)}'
            else:
                self.docker_image_name = _get_docker_image_name(docker_image_id)
            self.dockerfile_content = _get_dockerfile_content(dockerfile_path)

            if not os.path.exists(TWISTCLI_FILE_NAME):
                docker_image_scanning_integration.download_twistcli(TWISTCLI_FILE_NAME)
        except Exception as e:
            logging.error(f"Failed to setup docker image scanning\n{e}")
            raise e

    @staticmethod
    def cleanup_scan() -> None:
        os.remove(TWISTCLI_FILE_NAME)
        logging.info('twistcli file removed')

    @staticmethod
    def run_image_scan(docker_image_id: str) -> Dict[str, Any]:
        command = f"./{TWISTCLI_FILE_NAME} images scan --address {docker_image_scanning_integration.get_proxy_address()} --token {docker_image_scanning_integration.get_bc_api_key()} --details --output-file \"{DOCKER_IMAGE_SCAN_RESULT_FILE_NAME}\" {docker_image_id}"
        logging.debug(f"TwistCLI: {command}")
        subprocess.run(command, check=True, shell=True)  # nosec
        logging.info(f'TwistCLI ran successfully on image {docker_image_id}')

        with open(DOCKER_IMAGE_SCAN_RESULT_FILE_NAME) as docker_image_scan_result_file:
            scan_result = json.load(docker_image_scan_result_file)
        return scan_result

    def scan(self, docker_image_id: str, dockerfile_path: str, skip_extract_image_name: bool = False) -> None:
        try:
            self.setup_scan(docker_image_id, dockerfile_path, skip_extract_image_name)
            scan_result = self.run_image_scan(docker_image_id)
            docker_image_scanning_integration.report_results(
                twistcli_scan_result=scan_result,
                file_path=dockerfile_path,
                file_content=self.dockerfile_content,
                docker_image_name=self.docker_image_name,
            )
            logging.info('Docker image scanning results reported to the platform')
            self.cleanup_scan()
        except Exception as e:
            logging.error(f"Failed to scan docker image\n{e}")
            raise e


image_scanner = ImageScanner()
