# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
# SPDX-License-Identifier: GPL-2.0-or-later
"""Unit tests for the main functionality of the `debsigs` tool."""

from __future__ import annotations

import shutil
import subprocess  # noqa: S404
import typing

import pytest

from . import util
from .util import test_cfg  # noqa: F401  # pytest fixture


if typing.TYPE_CHECKING:
    import pathlib
    from typing import Final

    from testsigs import defs


def test_list(test_cfg: util.ConfigAggr) -> None:  # noqa: F811  # pytest fixture
    """Make sure `debsigs --list` on an unsigned package does not return anything."""
    with test_cfg.cfg_with_tempd() as cfg:
        if not test_cfg.has_feature("cmd-list", "1"):
            pytest.skip("No `debsigs --list` support")

        assert util.debsigs_list(cfg, test_cfg.changes_orig.debs[0]) == []


def _copy_and_sign_package(cfg: defs.Config, pkg_orig: pathlib.Path, sigtype: str) -> pathlib.Path:
    """Copy the package file, run `debsigs --sign`."""
    pkg_signed: Final = cfg.path.work / pkg_orig.name
    shutil.copy2(pkg_orig, pkg_signed)

    subprocess.check_call(  # noqa: S603
        [cfg.prog.debsigs, f"--sign={sigtype}", "--", pkg_signed.name],
        cwd=cfg.path.work,
        env=cfg.env,
    )

    cmp_res: Final = subprocess.run(  # noqa: S603
        ["cmp", "--", pkg_orig, pkg_signed],  # noqa: S607
        check=False,
        cwd=cfg.path.work,
        env=cfg.env,
    )
    if cmp_res.returncode == 0:
        raise RuntimeError(repr((cfg.prog.debsigs, pkg_orig, pkg_signed)))

    return pkg_signed


def _test_ar_sections(cfg: defs.Config, pkg_orig: pathlib.Path, pkg_signed: pathlib.Path) -> None:
    """Make sure the signed Debian package has exactly one more archive section."""
    sections_orig: Final = subprocess.check_output(  # noqa: S603
        ["ar", "t", "--", pkg_orig],  # noqa: S607
        cwd=cfg.path.work,
        encoding="UTF-8",
        env=cfg.env,
    ).splitlines()
    if any(name.startswith("_gpg") for name in sections_orig):
        raise RuntimeError(repr(sections_orig))

    sections_signed: Final = subprocess.check_output(  # noqa: S603
        ["ar", "t", "--", pkg_signed],  # noqa: S607
        cwd=cfg.path.work,
        encoding="UTF-8",
        env=cfg.env,
    ).splitlines()
    if sections_signed != [*sections_orig, "_gpgorigin"]:
        raise RuntimeError(repr((sections_orig, sections_signed)))


def _test_with_debsigs(tcfg: util.ConfigAggr, cfg: defs.Config, pkg_signed: pathlib.Path) -> None:
    """Run `debsigs --verify` now that it has actually been implemented."""
    if not tcfg.has_feature("cmd-verify", "1"):
        pytest.skip("No `debsigs --verify` support")

    subprocess.check_call(  # noqa: S603
        [cfg.prog.debsigs, "--verify", pkg_signed],
        cwd=cfg.path.work,
        env=cfg.env,
    )


def _test_with_debsig_verify(
    cfg: defs.Config,
    keys: defs.KeyFiles,
    pkg_signed: pathlib.Path,
    sign_fpr: str,
) -> None:
    """Prepare the policy file for `debsig-verify` and run it."""
    # Copied over from debsig-verify's `test/policies/*/generic.pol`
    policy_text: Final = f"""<?xml version="1.0"?>
<!DOCTYPE Policy SYSTEM "https://www.debian.org/debsig/1.0/policy.dtd">
<Policy xmlns="https://www.debian.org/debsig/1.0/">

  <!-- This is mainly a sanity check, since our filename is that of the ID
       anyway. -->
  <Origin Name="origin" id="{sign_fpr}" Description="Debsig testing"/>

  <!-- This is required to match in order for this policy to be used. We
       reject the release Type, since we want a different rule set for
       that. -->
  <Selection>
    <Required Type="origin" File="{keys.public_bin.name}" id="{sign_fpr}"/>
  </Selection>

  <!-- Once we decide to use this policy, this must pass in order to verify
       the package. -->
  <Verification MinOptional="0">
    <Required Type="origin" File="{keys.public_bin.name}" id="{sign_fpr}"/>
  </Verification>

</Policy>
"""
    print(policy_text)  # noqa: T201

    poldir: Final = cfg.path.work / "policies"
    poldir.mkdir(mode=0o755, exist_ok=True)
    polsub: Final = poldir / sign_fpr
    polsub.mkdir(mode=0o755, exist_ok=True)
    (polsub / "origin.pol").write_text(policy_text, encoding="UTF-8")

    shutil.copy2(keys.public_bin, polsub)

    subprocess.check_call(  # noqa: S603
        [
            cfg.prog.debsig_verify,
            "-d",
            "--policies-dir",
            poldir,
            "--keyrings-dir",
            poldir,
            "--list-policies",
            pkg_signed,
        ],
        cwd=cfg.path.work,
        env=cfg.env,
    )

    subprocess.check_call(  # noqa: S603
        [
            cfg.prog.debsig_verify,
            "-d",
            "--policies-dir",
            poldir,
            "--keyrings-dir",
            poldir,
            pkg_signed,
        ],
        cwd=cfg.path.work,
        env=cfg.env,
    )


def test_sign_and_verify(test_cfg: util.ConfigAggr) -> None:  # noqa: F811  # pytest fixture
    """Prepare the working directory, run the tests."""
    if not test_cfg.has_all_features({"cmd-list": "1", "cmd-sign": "1", "i-gpg": "1"}):
        pytest.skip("No `debsigs --list`, `--sign`, or GnuPG support")

    with test_cfg.cfg_with_tempd() as cfg:
        keys: Final = test_cfg.keys
        pub_key: Final = test_cfg.pub_key
        pkg_orig: Final = test_cfg.changes_orig.debs[0]

        sign_key: Final = util.get_signing_subkey(pub_key)

        sigs_empty: Final = util.debsigs_list(cfg, pkg_orig)
        if sigs_empty:
            raise RuntimeError(repr((pkg_orig, sigs_empty)))

        pkg_signed: Final = _copy_and_sign_package(cfg, pkg_orig, "origin")
        sigs_single_origin: Final = util.debsigs_list(cfg, pkg_signed)
        match sigs_single_origin:
            case [single] if single.sig_type == "origin" and single.key_id == sign_key.key_id:
                pass

            case _:
                raise RuntimeError(repr((pkg_signed, sigs_single_origin, pub_key)))

        _test_ar_sections(cfg, pkg_orig, pkg_signed)
        _test_with_debsigs(test_cfg, cfg, pkg_signed)
        _test_with_debsig_verify(cfg, keys, pkg_signed, sign_key.fpr)
