import click
import json
import hashlib
from convisoappsec.flowcli.context import pass_flow_context
from convisoappsec.common.graphql.errors import ResponseError

class ContainerVulnerabilityManager:
    """
    Manages the lifecycle of vulnerabilities detected in container/image scans, including closing and reopening.
    """

    def __init__(self):
        pass

    def close_vulnerability(self):
        """
        Close vulnerabilities on conviso platform when is not detected anymore.
        """
        result_file = "result.json"
        hash_issues = []

        log_func("Running automatic closure of vulnerabilities for image/container...")

        try:
            with open(result_file, 'r') as file:
                data = json.load(file)
                results = data.get('Results', [])

                for result in results:
                    if 'Vulnerabilities' in result and isinstance(result['Vulnerabilities'], list):
                        for vulnerability in result['Vulnerabilities']:
                            title = vulnerability.get('Title', '')
                            description = vulnerability.get('Description', '')
                            severity = vulnerability.get('Severity', '')
                            cve = vulnerability.get('VulnerabilityID', '')
                            package_name = vulnerability.get('PkgName', '')

                            hash_value = generate_hash_issue(title, description, severity, cve, package_name)
                            hash_issues.append(hash_value)
        except FileNotFoundError:
            print(f"Error: The file '{result_file}' was not found.")
        except json.JSONDecodeError:
            print(f"Error: Could not decode JSON from '{result_file}'. Check file format.")
        except Exception as e:
            print(f"An unexpected error occurred: {e}")

        issues_from_conviso = fetch_and_merge_conviso_issues()

        if len(issues_from_conviso) == 0:
            log_func("No vulnerabilities were found on the Conviso Platform!")
            return None

        issues_with_fix_accepted = [item for item in issues_from_conviso if item['status'] == 'FIX_ACCEPTED']
        issues_without_fix_accepted = [item for item in issues_from_conviso if item['status'] != 'FIX_ACCEPTED']
        set_of_hash_issues = set(hash_issues)

        close_issues(issues_from_cp=issues_without_fix_accepted, issues_from_current_scan=set_of_hash_issues)
        reopen_vulnerability(issues_with_fix_accepted=issues_with_fix_accepted, hash_issues=set_of_hash_issues)

        return None

def generate_hash_issue(title, description, severity, cve, package_name):
    """
    Generates a SHA256 hash based on the provided vulnerability details.
    """

    concatenated_string = f"{title}{description}{severity}{cve}{package_name}"

    return hashlib.sha256(concatenated_string.encode('utf-8')).hexdigest()

@pass_flow_context
def close_issues(flow_context, issues_from_cp, issues_from_current_scan):
    """
    method to close container issues on conviso platform

    # issues_from_cp are issues already on conviso platform.
    # issues_from_current_scan are issues identify on each time the ast command runs, these are always from a full code
    base scan
    """
    conviso_api = flow_context.create_conviso_api_client_beta()
    differences = [
        {'id': item['id'], 'originalIssueIdFromTool': item['originalIssueIdFromTool']}
        for item in issues_from_cp if item['originalIssueIdFromTool'] not in issues_from_current_scan
    ]

    if len(differences) == 0:
        log_func("No vulnerabilities have been fixed yet...")
        return

    log_func("Container: Fixing {issues} vulnerabilities on the Conviso Platform...".format(issues=len(differences)))

    for issue in differences:
        issue_id = issue['id']
        status = 'FIX_ACCEPTED'
        reason = ("The vulnerability is no longer found in the specified image. Its status has been updated by "
                  "Conviso AST")

        conviso_api.issues.update_issue_status(issue_id=issue_id, status=status, reason=reason)

@pass_flow_context
def reopen_vulnerability(flow_context, issues_with_fix_accepted, hash_issues):
    """
    Reopen vulnerabilities on conviso platform when is detected and already exists on conviso platform and
    was fixed in other moment.
    """
    conviso_api = flow_context.create_conviso_api_client_beta()

    issues_to_reopen = [
        {'id': item['id'], 'originalIssueIdFromTool': item['originalIssueIdFromTool']}
        for item in issues_with_fix_accepted if item['originalIssueIdFromTool'] in hash_issues
    ]

    if issues_to_reopen:
        log_func("Container: Reopening {issues} vulnerability/vulnerabilities on conviso platform ...".format(
            issues=len(issues_to_reopen))
        )

    for issue in issues_to_reopen:
        issue_id = issue['id']
        status = 'IDENTIFIED'
        reason = 'Status has been updated from Fixed to Identified by Conviso AST'

        conviso_api.issues.update_issue_status(issue_id=issue_id, status=status, reason=reason)

@pass_flow_context
@click.pass_context
def fetch_and_merge_conviso_issues(context, flow_context):
    page = 1
    conviso_api = flow_context.create_conviso_api_client_beta()
    company_id = context.params['company_id']
    asset_id = context.params['asset_id']
    statuses = ['CREATED', 'IDENTIFIED', 'IN_PROGRESS', 'AWAITING_VALIDATION', 'FIX_ACCEPTED']
    merged_issues = []

    while True:
        try:
            issues_from_cp = conviso_api.issues.auto_close_vulnerabilities(
                company_id, asset_id, statuses, page, vulnerability_type='CONTAINER_FINDING'
            )
        except ResponseError as error:
            if 'Variable $company_id' in str(error):
                log_func(f"Invalid company_id passed: {company_id}", fg='red')
            else:
                log_func(f"error: {error}", fg='red')

            log_func(
                "⚠️ Auto-close will not be performed at this time. "
                "Please set it using --company-id and try again.",
                fg='red'
            )
            return None

        total_pages = issues_from_cp['metadata']['totalPages']
        issues_collection = issues_from_cp['collection']

        issues_collection = [
            item for item in issues_collection
            if item.get('scanSource') == 'conviso_scanner'
        ]
        container_issues = [
            item for item in issues_collection
            if item.get('type') == 'CONTAINER_FINDING'
        ]

        if container_issues:
            merged_issues.extend(container_issues)

        if page >= total_pages:
            break
        page += 1

    return merged_issues

def log_func(msg, new_line=True, fg='white'):
    click.echo(click.style(msg, bold=True, fg=fg), nl=new_line, err=True)
