# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# src/pdftl/operations/dump_signatures.py

"""Dump and validate digital signatures from a PDF file using pyHanko"""

import io
import logging

import pdftl.core.constants as c
from pdftl.core.registry import register_operation
from pdftl.core.types import OpResult
from pdftl.utils.io_helpers import smart_open

logger = logging.getLogger(__name__)

_DUMP_SIGNATURES_LONG_DESC = """ Lists all digital signatures embedded
in the PDF and performs cryptographic validation on each.

If possible, this reads the PDF file directly from disk to ensure that
the cryptographic byte ranges are not disturbed by PDF parsing
engines.

### Signature Stanza Format

* `SignatureBegin`
* `SignatureFieldName`: The name of the signature field.
* `SignatureSigner`: Common Name (CN) of the signer.
* `SignatureHashAlgorithm`: e.g., sha256.
* `SignatureIntegrity`: VALID or INVALID (mathematical check).
* `SignatureCoverage`: ENTIRE_FILE, REVISION_ONLY, or PARTIAL.
* `SignatureModificationLevel`: NONE, FORM_FILLING, or SUSPICIOUS.
"""


def dump_signatures_cli_hook(result: OpResult, _stage):
    """
    CLI Hook for dump_signatures.
    Formats the list of signature dictionaries into the Stanza text format.
    """
    from pdftl.utils.hooks import from_result_meta

    output_file = from_result_meta(result, c.META_OUTPUT_FILE)
    signatures = result.data

    with smart_open(output_file) as out:
        if not signatures:
            print("No signatures found.", file=out)
            return

        for idx, sig_data in enumerate(signatures):
            print("SignatureBegin", file=out)
            print(f"SignatureFieldName: {sig_data.get('field_name')}", file=out)
            print(f"SignatureSigner: {sig_data.get('signer')}", file=out)
            print(f"SignatureHashAlgorithm: {sig_data.get('hash_algorithm')}", file=out)

            integrity = "VALID" if sig_data.get("is_valid") else "INVALID"
            print(f"SignatureIntegrity: {integrity}", file=out)

            print(f"SignatureCoverage: {sig_data.get('coverage')}", file=out)
            print(f"SignatureModificationLevel: {sig_data.get('modification_level')}", file=out)

            if idx + 1 < len(signatures):
                print("---", file=out)


@register_operation(
    "dump_signatures",
    tags=["info", "security", "signatures"],
    cli_hook=dump_signatures_cli_hook,
    type="single input operation",
    desc="List and validate digital signatures",
    long_desc=_DUMP_SIGNATURES_LONG_DESC,
    usage="<input> dump_signatures [output <output>]",
    # Pass filename and password to bypass pikepdf object modifications
    args=([c.INPUT_FILENAME, c.INPUT_PDF, c.INPUT_PASSWORD], {"output_file": c.OUTPUT}),
)
def dump_signatures(pdf_filename, pdf, pdf_password, output_file=None) -> OpResult:
    """
    Validate PDF signatures and returns a list of validation results.
    """
    # Mute pyHanko's internal validation log noise (tracebacks for self-signed certs)
    ph_logger = logging.getLogger("pyhanko")
    cv_logger = logging.getLogger("pyhanko_certvalidator")
    prev_ph, prev_cv = ph_logger.level, cv_logger.level
    ph_logger.setLevel(logging.CRITICAL)
    cv_logger.setLevel(logging.CRITICAL)

    try:
        signatures_data = _validate_signatures_worker(pdf_filename, pdf, pdf_password)
        return OpResult(success=True, data=signatures_data, meta={c.META_OUTPUT_FILE: output_file})
    finally:
        # Restore logging levels
        ph_logger.setLevel(prev_ph)
        cv_logger.setLevel(prev_cv)


def _validate_signatures_worker(pdf_filename, pdf, pdf_password):
    try:
        from pyhanko.pdf_utils.reader import PdfFileReader
        from pyhanko.sign.diff_analysis import DiffResult
        from pyhanko.sign.validation import validate_pdf_signature
    except ImportError:
        raise RuntimeError("The 'pyhanko' library is required for dump_signatures.")

    # pyHanko prefers reading the raw bytes to ensure signature integrity
    if pdf_filename != "_":
        with open(pdf_filename, "rb") as f:
            source_bytes = f.read()
    else:
        buf = io.BytesIO()
        pdf.save(buf)
        source_bytes = buf.getvalue()

    # 1. Initialize without the password argument
    reader = PdfFileReader(io.BytesIO(source_bytes))

    # 2. If a password was provided, decrypt the reader instance
    if reader.encrypted:
        password = pdf_password or ""
        reader.decrypt(password.encode("utf-8"))

    # 3. Access and validate signatures
    results = []

    for sig in reader.embedded_signatures:
        # Perform cryptographic validation
        status = validate_pdf_signature(sig)

        # Extract data into a clean dictionary
        signer_name = status.signing_cert.subject.native.get("common_name", "Unknown")

        if isinstance(status.diff_result, DiffResult):
            mod_level = status.diff_result.modification_level.name
        else:
            mod_level = f"SUSPICIOUS ({type(status.diff_result).__name__})"

        sig_data = {
            "field_name": sig.field_name,
            "signer": signer_name,
            "hash_algorithm": status.md_algorithm,
            "is_valid": status.intact,
            "coverage": status.coverage.name if status.coverage else "UNKNOWN_NONE",
            "modification_level": mod_level,
            # We could include more raw data here if needed by the API
        }
        results.append(sig_data)

    return results
