import io
import os
from dateutil import parser as date_parser
from typing import Dict, List
from pathlib import Path
from importlib.metadata import version
from functools import cache
from zipfile import ZipFile

import requests
from typer import echo
import yaml

from cli.utils import normalized_version
from cli.structures import Vulnerability


ADVISORY_DIR = Path(os.path.abspath(__file__)).parent.joinpath("advisories")
ADVISORY_TS = Path(os.path.abspath(__file__)).parent.joinpath("advisories").joinpath("ts")
vulnerability_index: Dict[str, Dict[str, List[Vulnerability]]] = {}

version_tag = version("ismyk8ssecure")


@cache
def get_current_ts():
    if not ADVISORY_TS.exists():
        return

    with open(ADVISORY_TS) as f:
        return date_parser.parse(f.read())


@cache
def get_latest_advisory_ts():
    resp = requests.get(
        f"https://api.github.com/repos/ismyk8ssecure/ismyk8ssecure/releases/tags/{version_tag}"
    )
    try:
        resp.raise_for_status()
    except Exception as e:
        echo(f"Got error while checking for new advisories {e}")
    resp = resp.json()
    for asset in resp["assets"]:
        if asset["name"] == "advisories.zip":
            return date_parser.parse(asset["updated_at"])


def is_there_new_advisory():
    if not ADVISORY_DIR.exists():
        return True

    curr_ts = get_current_ts()
    if not curr_ts:
        return True
    release_ts = get_latest_advisory_ts()
    return release_ts > curr_ts


def update_current_ts(new_ts):
    with open(ADVISORY_TS, "w") as f:
        f.write(new_ts.__str__())


def update_or_create_advisories():
    echo("Checking for new advisories")
    if not is_there_new_advisory():
        echo("Already using latest advisories")
        return
    echo("Found new advisories. Downloading...")

    resp = requests.get(
        f"https://github.com/ismyk8ssecure/ismyk8ssecure/releases/download/{version_tag}/advisories.zip"
    )
    try:
        resp.raise_for_status()
    except Exception as e:
        echo(f"Got error {e} while downloading latest advisories")
    f = io.BytesIO(resp.content)
    with ZipFile(f) as archive:
        try:
            os.rmdir(ADVISORY_DIR)
        except:
            pass
        archive.extractall(ADVISORY_DIR)
    update_current_ts(get_latest_advisory_ts())


def build_index():
    update_or_create_advisories()
    is_valid_advisory_file = lambda file_name: file_name.startswith("CVE-")
    for file in filter(is_valid_advisory_file, os.listdir(ADVISORY_DIR)):
        file_path = os.path.join(ADVISORY_DIR, file)
        load_index_with_file(file_path)
    echo("")


def update_index_with_advisory(advisory):
    for vulnerable_component in advisory["vulnerable_components"]:
        component_name = vulnerable_component["component_name"]
        if component_name not in vulnerability_index:
            vulnerability_index[component_name] = {}
        for vulnerable_version in vulnerable_component["vulnerable_versions"]:
            vulnerable_version = normalized_version(vulnerable_version)
            if vulnerable_version not in vulnerability_index[component_name]:
                vulnerability_index[component_name][vulnerable_version] = []
            vulnerability_index[component_name][vulnerable_version].append(
                Vulnerability(
                    cve_id=advisory["vulnerability_id"],
                    link=find_announce_link(advisory["references"]),
                )
            )


def load_index_with_file(file_path):
    with open(file_path) as f:
        advisory = yaml.safe_load(f)
        update_index_with_advisory(advisory)


def find_announce_link(links):
    for link in links:
        if "groups.google.com" in link:
            return link


build_index()
