import json
import logging
import os
from datetime import datetime, timedelta
from typing import Optional

from guarddog.analyzer.metadata.typosquatting import TyposquatDetector
from guarddog.utils.config import TOP_PACKAGES_CACHE_LOCATION
import requests

log = logging.getLogger("guarddog")


class NPMTyposquatDetector(TyposquatDetector):
    """Detector for typosquatting attacks. Detects if a package name is a typosquat of one of the top 5000 packages.
    Checks for distance one Levenshtein, one-off character swaps, permutations
    around hyphens, and substrings.

    Attributes:
        popular_packages (set): set of top 5000 most popular packages from npm
    """

    def _get_top_packages(self) -> set:

        popular_packages_url = (
            "https://github.com/LeoDog896/npm-rank/releases/download/latest/raw.json"
        )

        top_packages_filename = "top_npm_packages.json"

        resources_dir = TOP_PACKAGES_CACHE_LOCATION
        if resources_dir is None:
            resources_dir = os.path.abspath(
                os.path.join(os.path.dirname(__file__), "..", "resources")
            )

        top_packages_path = os.path.join(resources_dir, top_packages_filename)
        top_packages_information = self._get_top_packages_local(top_packages_path)

        if self._file_is_expired(top_packages_path, days=30):
            new_information = self._get_top_packages_network(popular_packages_url)
            if new_information is not None:
                top_packages_information = new_information

                with open(top_packages_path, "w+") as f:
                    json.dump(new_information, f, ensure_ascii=False, indent=4)

        if top_packages_information is None:
            return set()
        return set(top_packages_information)

    def _file_is_expired(self, path: str, days: int) -> bool:
        try:
            update_time = datetime.fromtimestamp(os.path.getmtime(path))
            return datetime.now() - update_time > timedelta(days=days)
        except FileNotFoundError:
            return True

    def _get_top_packages_local(self, path: str) -> list[dict] | None:
        try:
            with open(path, "r") as f:
                result = json.load(f)
                return result
        except FileNotFoundError:
            log.error(f"File not found: {path}")
            return None

    def _get_top_packages_network(self, url: str) -> list[dict] | None:
        try:
            response = requests.get(url)
            response.raise_for_status()

            response_data = response.json()
            result = list([i["name"] for i in response_data[0:8000]])

            return result
        except json.JSONDecodeError:
            log.error(f'Couldn`t convert to json: "{response.text}"')
            return None
        except requests.exceptions.RequestException as e:
            log.error(f"Network error: {e}")
            return None

    def detect(
        self,
        package_info,
        path: Optional[str] = None,
        name: Optional[str] = None,
        version: Optional[str] = None,
    ) -> tuple[bool, Optional[str]]:
        """
        Uses a package's information from PyPI's JSON API to determine the
        package is attempting a typosquatting attack

        Args:
            package_info (dict): dictionary representation of PyPI's JSON
                output

        Returns:
            list[str]: names of packages that <package_name> could be
            typosquatting from
            @param **kwargs:
        """

        similar_package_names = self.get_typosquatted_package(package_info["name"])
        if len(similar_package_names) > 0:
            return True, TyposquatDetector.MESSAGE_TEMPLATE % ", ".join(
                similar_package_names
            )
        return False, None

    def _get_confused_forms(self, package_name) -> list:
        """Gets confused terms for npm packages.
        Currently, there are no confused terms for npm packages.
        """
        return []


if __name__ == "__main__":
    # update top_npm_packages.json
    NPMTyposquatDetector()._get_top_packages()
