from typing import List

import click
import github
from github.ContentFile import ContentFile
from github.GithubException import GithubException
from github.Rate import Rate
from github.RateLimit import RateLimit

from ghsearch.filters import Filter, FilterException
from ghsearch.terminal import ProgressPrinter

CORE_CALLS_RELATIVE_LIMIT = 0.1
CORE_CALLS_ABSOLUTE_LIMIT = 500


def _confirm_continue_many_calls(core_rate: Rate, num_results: int, calls_per_res: int) -> None:
    click.confirm(
        f"""
Warning: you are about to potentially make more than {CORE_CALLS_ABSOLUTE_LIMIT} core api requests.
Your search yielded {num_results} results, and gh-search may make up to {calls_per_res} core api call(s) per result.

Your current core api usage is {core_rate.remaining}/{core_rate.limit} (resets {core_rate.reset})

Do you want to continue?""".strip(),
        abort=True,
    )


def _confirm_continue_near_limit(core_rate: Rate, num_results: int, calls_per_res: int) -> None:
    click.confirm(
        f"""
Warning: you are at risk of going below {CORE_CALLS_RELATIVE_LIMIT:.0%} of your remaining core api rate limit.
Your search yielded {num_results} results, and gh-search may make up to {calls_per_res} core api call(s) per result.

Your current core api usage is {core_rate.remaining}/{core_rate.limit} (resets {core_rate.reset})

Do you want to continue?""".strip(),
        abort=True,
    )


def _echo_rate_limits(rate_limit: RateLimit) -> None:
    click.echo(
        f"Core rate limit: {rate_limit.core.remaining}/{rate_limit.core.limit} (resets {rate_limit.core.reset}), "
        f"Search rate limit: {rate_limit.search.remaining}/{rate_limit.search.limit} (resets {rate_limit.search.reset})"
    )


class GHSearch:
    def __init__(self, client: github.Github, filters: List[Filter], verbose: bool = False):
        self.client = client
        self.filters = filters
        self.verbose = verbose

    def get_rate_limit(self) -> RateLimit | None:
        try:
            return self.client.get_rate_limit()
        except GithubException as ge:
            # 404 means that rate limiting is disabled
            if ge.status == 404:
                return None
            raise ge

    def get_filtered_results(self, query: List[str]) -> List[ContentFile]:
        rate_limit = self.get_rate_limit()

        if rate_limit and self.verbose:
            _echo_rate_limits(rate_limit)

        results = self.client.search_code(query=" ".join(query))

        if rate_limit:
            self._check_core_limit_threshold(results.totalCount, rate_limit.core)

        filtered_results = []
        with ProgressPrinter(overwrite=not self.verbose) as printer:
            for result in results:
                printer(f"Checking result for {result.repository.full_name}")
                try:
                    exclude_reason = self._should_exclude(result)
                except FilterException as e:
                    printer(str(e), force=True)
                else:
                    if not exclude_reason:
                        filtered_results.append(result)
                    elif self.verbose:
                        click.echo(f"Skipping result for {result.repository.full_name} via {exclude_reason}")

        rate_limit = self.get_rate_limit()
        if rate_limit and self.verbose:
            _echo_rate_limits(rate_limit)

        return filtered_results

    def _should_exclude(self, result):
        for result_filter in self.filters:
            if not result_filter(result):
                return result_filter.__class__.__name__
        return False

    def _check_core_limit_threshold(self, num_results: int, core_rate: Rate) -> None:
        max_core_api_calls_per_result = sum(bool(f.uses_core_api) for f in self.filters)
        if max_core_api_calls_per_result > 0:

            num_core_api_calls_worst_case = num_results * max_core_api_calls_per_result
            remaining_worst_case = core_rate.remaining - num_core_api_calls_worst_case
            if remaining_worst_case / core_rate.limit < CORE_CALLS_RELATIVE_LIMIT:
                _confirm_continue_near_limit(core_rate, num_results, max_core_api_calls_per_result)
            elif num_core_api_calls_worst_case > CORE_CALLS_ABSOLUTE_LIMIT:
                _confirm_continue_many_calls(core_rate, num_results, max_core_api_calls_per_result)
