# Copyright 1999-2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import errno
import io
import sys

from _emerge.CompositeTask import CompositeTask
import portage
from portage import os
from portage.checksum import (
    _apply_hash_filter,
    _filter_unaccelarated_hashes,
    _hash_filter,
)
from portage.output import EOutput
from portage.util._async.FileDigester import FileDigester
from portage.package.ebuild.fetch import _checksum_failure_temp_file


class BinpkgVerifier(CompositeTask):
    __slots__ = ("logfile", "pkg", "_digests", "_pkg_path")

    def _start(self):
        bintree = self.pkg.root_config.trees["bintree"]
        digests = bintree._get_digests(self.pkg)
        if "size" not in digests:
            self.returncode = os.EX_OK
            self._async_wait()
            return

        digests = _filter_unaccelarated_hashes(digests)
        hash_filter = _hash_filter(bintree.settings.get("PORTAGE_CHECKSUM_FILTER", ""))
        if not hash_filter.transparent:
            digests = _apply_hash_filter(digests, hash_filter)

        self._digests = digests

        try:
            size = os.stat(self._pkg_path).st_size
        except OSError as e:
            if e.errno not in (errno.ENOENT, errno.ESTALE):
                raise

            # We might end up here with FEATURES="pkgdir-index-trusted" if
            # binpkgs have been removed manually without refreshing the index.
            if bintree.dbapi.cpv_exists(self.pkg.cpv):
                self.scheduler.output(
                    f"!!! Tried to use non-existent binary for '{self.pkg.cpv}'\n"
                    + "!!! Likely caused by an outdated index. Run 'emaint binhost -f'.\n",
                    log_path=self.logfile,
                    background=self.background,
                )
            else:
                self.scheduler.output(
                    f"!!! Fetching Binary failed for '{self.pkg.cpv}'\n",
                    log_path=self.logfile,
                    background=self.background,
                )

            self.returncode = 1
            self._async_wait()
            return
        else:
            if size != digests["size"]:
                self._digest_exception("size", size, digests["size"])
                self.returncode = 1
                self._async_wait()
                return

        self._start_task(
            FileDigester(
                file_path=self._pkg_path,
                hash_names=[k for k in digests if k != "size"],
                background=self.background,
                logfile=self.logfile,
                scheduler=self.scheduler,
            ),
            self._digester_exit,
        )

    def _digester_exit(self, digester):
        if self._default_exit(digester) != os.EX_OK:
            self.wait()
            return

        for hash_name in digester.hash_names:
            if digester.digests[hash_name] != self._digests[hash_name]:
                self._digest_exception(
                    hash_name, digester.digests[hash_name], self._digests[hash_name]
                )
                self.returncode = 1
                self.wait()
                return

        if self.pkg.root_config.settings.get("PORTAGE_QUIET") != "1":
            self._display_success()

        self.returncode = os.EX_OK
        self.wait()

    def _display_success(self):
        stdout_orig = sys.stdout
        stderr_orig = sys.stderr
        global_havecolor = portage.output.havecolor
        out = io.StringIO()
        try:
            sys.stdout = out
            sys.stderr = out
            if portage.output.havecolor:
                portage.output.havecolor = not self.background

            path = self._pkg_path
            if path.endswith(".partial"):
                path = path[: -len(".partial")]
            eout = EOutput()
            eout.ebegin(
                f"{os.path.basename(path)} {' '.join(sorted(self._digests))} ;-)"
            )
            eout.eend(0)

        finally:
            sys.stdout = stdout_orig
            sys.stderr = stderr_orig
            portage.output.havecolor = global_havecolor

        self.scheduler.output(
            out.getvalue(), log_path=self.logfile, background=self.background
        )

    def _digest_exception(self, name, value, expected):
        head, tail = os.path.split(self._pkg_path)
        temp_filename = _checksum_failure_temp_file(
            self.pkg.root_config.settings, head, tail
        )

        self.scheduler.output(
            "\n!!! Digest verification failed:\n"
            f"!!! {self._pkg_path}\n"
            f"!!! Reason: Failed on {name} verification\n"
            f"!!! Got: {value}\n"
            f"!!! Expected: {expected}\n"
            f"File renamed to '{temp_filename}'\n",
            log_path=self.logfile,
            background=self.background,
        )
