"""
Attestation Verification Module for Verisoo.

This module answers the fundamental question:
    "Was this image attested by this identity at this time?"

Verification is the counterpart to attestation - it validates that an image
matches a known attestation record and that the record's signature is valid.

Verification Architecture:
-------------------------

    ┌─────────────────────────────────────────────────────────────────────┐
    │                      VERIFICATION PIPELINE                          │
    └─────────────────────────────────────────────────────────────────────┘

         Input Image                     Attestation Record
              │                                  │
              ▼                                  │
    ┌─────────────────┐                          │
    │  Compute Hashes │                          │
    │  (SHA + pHash)  │                          │
    └────────┬────────┘                          │
             │                                   │
             ▼                                   ▼
    ┌─────────────────────────────────────────────────┐
    │           STEP 1: HASH COMPARISON               │
    │  ┌─────────────┐        ┌─────────────┐         │
    │  │ Image Hash  │ ─────► │ Record Hash │         │
    │  └─────────────┘  ??    └─────────────┘         │
    │                                                 │
    │  Exact match:      SHA-256 identical            │
    │  Perceptual match: pHash/dHash within threshold │
    └───────────────────────┬─────────────────────────┘
                            │
                            ▼
    ┌─────────────────────────────────────────────────┐
    │         STEP 2: IDENTITY LOOKUP                 │
    │                                                 │
    │  Fingerprint ─────► Identity Database           │
    │                     (resolve public key)        │
    └───────────────────────┬─────────────────────────┘
                            │
                            ▼
    ┌─────────────────────────────────────────────────┐
    │         STEP 3: SIGNATURE VERIFICATION          │
    │                                                 │
    │  ┌────────────┐    ┌───────────────┐            │
    │  │ Public Key │ ─► │ Verify Ed25519│ ◄─ Record  │
    │  └────────────┘    │   Signature   │   Signature│
    │                    └───────┬───────┘            │
    │                            │                    │
    │                     Valid / Invalid             │
    └───────────────────────┬─────────────────────────┘
                            │
                            ▼
                  ┌─────────────────┐
                  │ Verification    │
                  │ Result          │
                  └─────────────────┘

Match Types:
-----------
Verisoo uses multiple matching strategies to handle real-world image sharing:

1. **Exact Match** (SHA-256)
   - Byte-for-byte identical images
   - Fails after ANY modification (even metadata changes)
   - Use case: Verifying untouched originals

2. **Perceptual Match** (pHash, dHash)
   - Based on visual content, not bytes
   - Survives: JPEG recompression, resizing, format conversion
   - Use case: Verifying social media copies

Perceptual Threshold:
-------------------
The threshold parameter controls how similar images must be for perceptual matching:

    threshold=0  : Exact perceptual match (rare for different encodings)
    threshold=5  : Very strict - minor differences only
    threshold=10 : Default - handles most social media compression
    threshold=15 : Looser - catches more heavily modified images
    threshold=20+: Very loose - may have false positives

The threshold is measured in Hamming distance (bit differences between hashes).
Lower = more strict, higher = more tolerant.

Usage Examples:
--------------
    from .verification import verify_attestation, find_attestations_for_image

    # Full verification with identity lookup
    result = verify_attestation(
        image_data=downloaded_image,
        record=attestation_record,
        lookup_identity=storage.load_identity,
    )
    if result.is_valid:
        print(f"Verified! Attested by {result.attestor.display_name}")

    # Find all attestations for an image
    matches = find_attestations_for_image(image_data, all_records)
    for record in matches:
        print(f"Found attestation from {record.timestamp}")
"""

from __future__ import annotations

from typing import Callable

from .crypto import fingerprint_from_pubkey, verify
from .exceptions import HashMismatchError, SignatureError, VerificationError
from .hashing import hash_image, is_same_image
from .models import (
    AttestationRecord,
    Identity,
    ImageHashes,
    VerificationResult,
)


# =============================================================================
# TYPE DEFINITIONS
# =============================================================================

# Identity lookup function type
# Takes a fingerprint string, returns Identity object or None if unknown
IdentityLookup = Callable[[str], Identity | None]


# =============================================================================
# MAIN VERIFICATION FUNCTION
# =============================================================================


def verify_attestation(
    image_data: bytes,
    record: AttestationRecord,
    lookup_identity: IdentityLookup,
    *,
    perceptual_threshold: int = 10,
    require_exact_match: bool = False,
) -> VerificationResult:
    """
    Verify an attestation record against an image.

    This is the primary verification function. It performs a complete
    verification pipeline to determine if an image matches an attestation
    record and if the record's signature is valid.

    Verification Steps:
    ------------------
    1. **Hash Computation**: Compute SHA-256 and perceptual hashes of the
       provided image.

    2. **Hash Comparison**: Compare computed hashes against the record's
       hashes. Supports both exact (SHA-256) and perceptual (pHash/dHash)
       matching.

    3. **Identity Lookup**: Resolve the attestor's fingerprint to their
       public key using the provided lookup function.

    4. **Signature Verification**: Rebuild the canonical signing payload
       and verify the Ed25519 signature against the attestor's public key.

    Match Types Returned:
    --------------------
    - "exact": SHA-256 bytes match (images are identical)
    - "phash": Perceptual hash match (DCT-based, survives JPEG)
    - "dhash": Difference hash match (survives resizing)
    - None: No match found

    Args:
        image_data:
            Raw bytes of the image to verify. Can be any PIL-supported
            format (JPEG, PNG, WebP, etc.).

        record:
            AttestationRecord to verify against. Contains the expected
            hashes, signature, and attestor fingerprint.

        lookup_identity:
            Callable that takes a fingerprint string and returns an
            Identity object (with public key), or None if unknown.
            Typically: storage.load_identity

        perceptual_threshold:
            Maximum Hamming distance for perceptual hash matching.
            - 0: Exact perceptual match only
            - 10: Default, handles typical social media compression
            - 20: Loose, may have false positives
            Higher values are more tolerant of image modifications.

        require_exact_match:
            If True, only accept SHA-256 exact matches. Perceptual
            matches will be rejected. Use for high-security scenarios.

    Returns:
        VerificationResult object with:
        - is_valid: True if all verification steps passed
        - match_type: How the image matched ("exact", "phash", etc.)
        - attestor: Identity object if found
        - record: The original record (for convenience)
        - error: Error message if verification failed

    Failure Scenarios:
    -----------------
    - Image doesn't match (no hash match within threshold)
    - Unknown attestor (fingerprint not in identity database)
    - Fingerprint mismatch (identity's key doesn't match fingerprint)
    - Invalid signature (record was tampered with)

    Example:
        >>> result = verify_attestation(
        ...     image_data=suspect_image,
        ...     record=claimed_attestation,
        ...     lookup_identity=storage.load_identity,
        ... )
        >>> if result.is_valid:
        ...     print(f"Verified by {result.attestor.display_name}")
        ...     print(f"Match type: {result.match_type}")
        ... else:
        ...     print(f"Verification failed: {result.error}")
    """
    # -------------------------------------------------------------------------
    # STEP 1: Compute hashes of the provided image
    # This gives us SHA-256 (exact) and perceptual hashes to compare
    # -------------------------------------------------------------------------
    image_hashes = hash_image(image_data)

    # -------------------------------------------------------------------------
    # STEP 2: Compare hashes against the attestation record
    # is_same_image() tries exact match first, then perceptual hashes
    # -------------------------------------------------------------------------
    is_same, match_type = is_same_image(
        image_hashes,
        record.image_hashes,
        perceptual_threshold=perceptual_threshold,
    )

    # No match found - images are different
    if not is_same:
        return VerificationResult(
            is_valid=False,
            match_type=None,
            attestor=None,
            record=record,
            error="Image does not match attestation",
            hash_distances={
                "provided_sha256": image_hashes.sha256,
                "record_sha256": record.image_hashes.sha256,
            },
        )

    # Normalize match_type for consistent API
    # Internal "exact_match" becomes external "exact"
    normalized_match_type = "exact" if match_type == "exact_match" else match_type

    # If caller requires exact match, reject perceptual matches
    if require_exact_match and normalized_match_type != "exact":
        return VerificationResult(
            is_valid=False,
            match_type=normalized_match_type,
            attestor=None,
            record=record,
            error="Exact match required but only perceptual match found",
        )

    # -------------------------------------------------------------------------
    # STEP 3: Look up the attestor's identity
    # We need their public key to verify the signature
    # -------------------------------------------------------------------------
    identity = lookup_identity(record.attestor_fingerprint)

    if identity is None:
        # Attestor not in our identity database
        # Could be from a peer we haven't synced with yet
        return VerificationResult(
            is_valid=False,
            match_type=normalized_match_type,
            attestor=None,
            record=record,
            error=f"Unknown attestor: {record.attestor_fingerprint}",
        )

    # Verify that the identity's public key actually produces this fingerprint
    # This catches mismatched or corrupted identity records
    computed_fingerprint = fingerprint_from_pubkey(identity.public_key)
    if computed_fingerprint != record.attestor_fingerprint:
        return VerificationResult(
            is_valid=False,
            match_type=normalized_match_type,
            attestor=identity,
            record=record,
            error="Identity fingerprint mismatch",
        )

    # -------------------------------------------------------------------------
    # STEP 4: Verify the Ed25519 signature
    # Rebuild the canonical payload and check the signature
    # -------------------------------------------------------------------------
    from .attestation import _build_signing_payload

    # Reconstruct the exact bytes that were signed
    signing_payload = _build_signing_payload(
        record.image_hashes,
        record.attestor_fingerprint,
        record.timestamp,
        record.metadata,
    )

    try:
        # Ed25519 signature verification
        # Raises SignatureError if invalid
        verify(identity.public_key, record.signature, signing_payload)
    except SignatureError as e:
        # Signature invalid - record may have been tampered with
        return VerificationResult(
            is_valid=False,
            match_type=normalized_match_type,
            attestor=identity,
            record=record,
            error=str(e),
        )

    # -------------------------------------------------------------------------
    # SUCCESS: All checks passed
    # -------------------------------------------------------------------------
    return VerificationResult(
        is_valid=True,
        match_type=normalized_match_type,
        attestor=identity,
        record=record,
        error=None,
    )


# =============================================================================
# SIGNATURE-ONLY VERIFICATION
# =============================================================================


def verify_record_signature(
    record: AttestationRecord,
    identity: Identity,
) -> bool:
    """
    Verify just the signature on an attestation record.

    This is a lightweight verification that checks ONLY whether the signature
    is valid for the given identity. It does NOT:
    - Check if an image matches the record's hashes
    - Look up the identity (caller provides it)
    - Verify fingerprint matches public key

    Use Cases:
    ---------
    - **Log validation**: Verify all records in a log weren't tampered with
    - **Sync verification**: Check records received from peers
    - **Batch processing**: When you already have the identity loaded

    Why No Image Check?
    ------------------
    Sometimes you want to verify record integrity without having the original
    image. For example, when validating a log backup or checking records
    received over the network.

    Args:
        record: AttestationRecord to verify
        identity: Identity object with the attestor's public key

    Returns:
        True if signature is valid, False otherwise

    Example:
        >>> # Validate all records in a log
        >>> for record in storage.iterate_records():
        ...     identity = storage.load_identity(record.attestor_fingerprint)
        ...     if identity and not verify_record_signature(record, identity):
        ...         print(f"TAMPERED: {record.record_id}")
    """
    from .attestation import _build_signing_payload

    # Rebuild the canonical payload that was originally signed
    signing_payload = _build_signing_payload(
        record.image_hashes,
        record.attestor_fingerprint,
        record.timestamp,
        record.metadata,
    )

    try:
        # Verify Ed25519 signature
        verify(identity.public_key, record.signature, signing_payload)
        return True
    except SignatureError:
        # Signature invalid - record was tampered or key mismatch
        return False


# =============================================================================
# ATTESTATION SEARCH
# =============================================================================


def find_attestations_for_image(
    image_data: bytes,
    records: list[AttestationRecord],
    *,
    perceptual_threshold: int = 10,
) -> list[AttestationRecord]:
    """
    Find all attestation records that match an image.

    This function searches through a list of attestation records and returns
    all that match the provided image (either exactly or perceptually).

    The returned records are sorted by timestamp (oldest first), which
    establishes the **provenance chain** - showing the history of who
    attested this image and when.

    Matching Strategy:
    -----------------
    For each record, we check:
    1. Exact SHA-256 match (byte-identical)
    2. Perceptual hash match within threshold (visually same)

    If either matches, the record is included in results.

    Use Cases:
    ---------
    - **Fact-checking**: Find who first attested this viral image
    - **Provenance chain**: See all attestations for an image over time
    - **Duplicate detection**: Find if image was previously attested

    Args:
        image_data:
            Raw bytes of the image to search for.

        records:
            List of AttestationRecords to search through.
            Typically: list(storage.iterate_records())

        perceptual_threshold:
            Maximum Hamming distance for perceptual matching.
            Default 10 catches most social media compression.

    Returns:
        List of matching AttestationRecords, sorted by timestamp
        (oldest first). Empty list if no matches found.

    Performance Note:
        This performs a linear scan of all records. For large databases,
        consider using indexed lookups (by SHA-256 or perceptual hash)
        in the storage layer.

    Example:
        >>> # Find all attestations for a downloaded image
        >>> image = open("suspect_photo.jpg", "rb").read()
        >>> records = list(storage.iterate_records())
        >>> matches = find_attestations_for_image(image, records)
        >>>
        >>> if matches:
        ...     first = matches[0]
        ...     print(f"First attested: {first.timestamp}")
        ...     print(f"By: {first.attestor_fingerprint[:16]}...")
        ...     if first.location:
        ...         print(f"Location: {first.location}")
        ... else:
        ...     print("No attestations found for this image")
    """
    # Compute hashes of the query image once (expensive operation)
    image_hashes = hash_image(image_data)
    matches = []

    # Linear scan through all records
    for record in records:
        is_same, _ = is_same_image(
            image_hashes,
            record.image_hashes,
            perceptual_threshold=perceptual_threshold,
        )
        if is_same:
            matches.append(record)

    # Sort by timestamp (oldest first) to show provenance chain
    # The first attestation is the most significant for establishing provenance
    return sorted(matches, key=lambda r: r.timestamp)
