from juriscraper.AbstractSite import logger

from cl.scrapers.models import UrlHash


class DupChecker(dict):
    def __init__(
        self, court, full_crawl=False, dup_threshold=5, *args, **kwargs
    ):
        self.full_crawl = full_crawl
        self.court = court
        self.dup_threshold = dup_threshold
        self.url_hash = None
        self.dup_count = 0
        self.last_found_date = None
        self.emulate_break = False
        super(DupChecker, self).__init__(*args, **kwargs)

    def _increment(self, current_date):
        """Increments the dup_count and sets the correct date for the latest
        dup.
        """
        self.last_found_date = current_date
        self.dup_count += 1

    def METHOD_NAME(self):
        """Resets the dup counter and date"""
        self.dup_count = 0
        self.last_found_date = None

    def update_site_hash(self, hash):
        self.url_hash.sha1 = hash
        self.url_hash.save()

    def _court_changed(self, url, hash):
        """Determines whether a court website has changed since we last saw it.

        Takes a hash generated by Juriscraper and compares that hash to a value
        in the DB, if there is one. If there is a value and it is the same, it
        returns False. Else, it returns True.
        """
        url_hash, created = UrlHash.objects.get_or_create(pk=url)
        if not created and url_hash.sha1 == hash:
            # it wasn't created, and it has the same SHA --> not changed.
            return False, url_hash
        else:
            # It's a known URL or it's a changed hash.
            return True, url_hash

    def abort_by_url_hash(self, url, hash):
        """Checks whether we should abort due to a hash of the site data being
        unchanged since the last time a URL was visited.

        Returns True if we should abort the crawl. Else, returns False. Creates
        the item in the database if it doesn't already exist, assigning it to
        self.url2Hash.
        """
        changed, self.url_hash = self._court_changed(url, hash)
        if not self.full_crawl:
            if not changed:
                logger.info(f"Unchanged hash at: {url}")
                return True
            else:
                logger.info(f"Identified changed hash at: {url}")
                return False
        else:
            # If it's a full crawl, we don't care about the hash. We continue
            # no matter what.
            return False

    def press_on(
        self,
        object_type,
        current_date,
        next_date,
        lookup_value,
        lookup_by="sha1",
    ):
        """Checks if a we have an `object_type` with identical content in the CL
        corpus by looking up `lookup_value` in the `lookup_by` field. Depending
        on the result of that, we either return True or False. True represents
        the fact that the next item should be processed. False means that either
        the item was a duplicate or that we've hit so many duplicates that we've
        stopped checking (we hit a duplicate threshold). Either way, the caller
        should move to the next item and try it.

        The effect of this is that this emulates for loop constructs for
        continue (False), break (False), return (True).

        Following logic applies:
         - if we have the item already
            - and if the next date is before this date
            - or if this is our duplicate threshold is exceeded
                - break
            - otherwise
                - continue
         - if not
            - carry on
        """
        if self.emulate_break:
            return False

        # check for a duplicate in the db.
        if lookup_by == "sha1":
            exists = object_type.objects.filter(sha1=lookup_value).exists()
        elif lookup_by == "download_url":
            exists = object_type.objects.filter(
                download_url=lookup_value
            ).exists()
        else:
            raise NotImplementedError("Unknown lookup_by parameter.")

        if exists:
            logger.info(
                "Duplicate found on date: %s, with lookup value: %s"
                % (current_date, lookup_value)
            )
            self._increment(current_date)

            # If the next date in the Site object is less than (before) the
            # current date, we needn't continue because we should already have
            # that item.
            if next_date:
                already_scraped_next_date = next_date < current_date
            else:
                already_scraped_next_date = True
            if not self.full_crawl:
                if already_scraped_next_date:
                    if self.court.pk == "mich":
                        # Michigan sometimes has multiple occurrences of the
                        # same case with different dates on a page.
                        return False
                    else:
                        logger.info(
                            "Next case occurs prior to when we found a "
                            "duplicate. Court is up to date."
                        )
                        self.emulate_break = True
                        return False
                elif self.dup_count >= self.dup_threshold:
                    logger.info(
                        "Found %s duplicates in a row. Court is up to date."
                        % self.dup_count
                    )
                    self.emulate_break = True
                    return False
            else:
                # This is a full crawl. Do not emulate a break, BUT be sure to
                # say that we shouldn't press on, since the item already exists.
                return False
        else:
            return True