import cv2
import numpy as np
import hashlib
import pytesseract
import sys

# ----------------------------------------------------------------------
# Configuration
HASH_POS = (50, 50)          # top-left corner where hash is drawn
HASH_FONT = cv2.FONT_HERSHEY_SIMPLEX
HASH_SCALE = 0.6
HASH_COLOR = (0, 0, 255)     # red
HASH_THICKNESS = 2
BACKGROUND_RECT = True       # draw white rectangle behind text for OCR
RECT_PADDING = 5

# ----------------------------------------------------------------------
def frame_to_bytes(frame):
    """Convert OpenCV frame (BGR) to bytes for hashing."""
    return frame.tobytes()

def compute_hash(frame):
    """Return SHA256 hex digest of the frame's raw bytes."""
    return hashlib.sha256(frame_to_bytes(frame)).hexdigest()

def draw_text_with_background(img, text, pos, font, scale, color, thickness):
    """Draw text with a solid background rectangle for readability/OCR."""
    x, y = pos
    (tw, th), baseline = cv2.getTextSize(text, font, scale, thickness)
    # background rectangle
    cv2.rectangle(img, (x - RECT_PADDING, y - th - RECT_PADDING),
                  (x + tw + RECT_PADDING, y + baseline + RECT_PADDING),
                  (255, 255, 255), -1)
    cv2.putText(img, text, (x, y), font, scale, color, thickness)

def extract_hash_from_frame(frame, pos, font, scale, thickness):
    """
    Extract the visible hash from a frame using OCR.
    Returns hex string or None if not found.
    """
    x, y = pos
    # estimate region where text was drawn (with padding)
    # we need a generous region because we don't know text length beforehand.
    # For simplicity, take a fixed large region (e.g., 400x100 around pos)
    roi = frame[max(0, y-50):y+50, max(0, x-10):x+400]
    if roi.size == 0:
        return None
    # convert to grayscale and threshold for better OCR
    gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
    _, thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)
    text = pytesseract.image_to_string(thresh, config='--psm 7 -c tessedit_char_whitelist=0123456789abcdef')
    # clean: keep only hex characters
    clean = ''.join(c for c in text if c in '0123456789abcdef')
    # SHA256 hex is 64 chars
    if len(clean) >= 64:
        return clean[:64]
    return None

# ----------------------------------------------------------------------
def encode_video(input_path, output_path):
    """Embed visible hash chain into video."""
    cap = cv2.VideoCapture(input_path)
    if not cap.isOpened():
        print("Error: cannot open input video")
        return False

    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

    prev_hash = None
    prev_frame = None
    frame_idx = 0

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        # Make a working copy (we will write the overlay on it)
        out_frame = frame.copy()

        if prev_frame is not None:
            # Compute hash of the PREVIOUS frame (which already contains its overlay)
            h = compute_hash(prev_frame)
            # Draw that hash onto the current frame
            draw_text_with_background(out_frame, h, HASH_POS, HASH_FONT,
                                      HASH_SCALE, HASH_COLOR, HASH_THICKNESS)
            print(f"Frame {frame_idx}: displayed hash of frame {frame_idx-1} = {h[:16]}...")
        else:
            # First frame: display a fixed anchor hash (hash of the string "ANCHOR")
            anchor = hashlib.sha256(b"FIRST_FRAME_ANCHOR").hexdigest()
            draw_text_with_background(out_frame, anchor, HASH_POS, HASH_FONT,
                                      HASH_SCALE, HASH_COLOR, HASH_THICKNESS)
            print(f"Frame 0: anchor hash = {anchor[:16]}...")

        out.write(out_frame)

        # Update for next iteration
        prev_frame = out_frame   # the frame with its overlay becomes the "previous" for next frame
        frame_idx += 1

    cap.release()
    out.release()
    print(f"Encoded video saved to {output_path}")
    return True

# ----------------------------------------------------------------------
def verify_video(input_path):
    """Verify the visible hash chain from the encoded video."""
    cap = cv2.VideoCapture(input_path)
    if not cap.isOpened():
        print("Error: cannot open video")
        return False

    frames = []
    while True:
        ret, frame = cap.read()
        if not ret:
            break
        frames.append(frame)
    cap.release()

    if len(frames) < 2:
        print("Video has fewer than 2 frames, cannot verify chain")
        return False

    # For each frame i (starting from 0), extract its displayed hash (which should be hash of frame i-1)
    # but frame 0 displays anchor hash.
    # We'll verify for i >= 1: displayed hash on frame i should equal compute_hash(frame i-1)
    verified = True
    for i in range(1, len(frames)):
        displayed = extract_hash_from_frame(frames[i], HASH_POS, HASH_FONT,
                                            HASH_SCALE, HASH_THICKNESS)
        if displayed is None:
            print(f"Frame {i}: could not extract hash (OCR failed)")
            verified = False
            continue

        # Compute actual hash of previous frame (frame i-1)
        actual = compute_hash(frames[i-1])
        if displayed != actual:
            print(f"Frame {i}: MISMATCH!")
            print(f"  Displayed: {displayed}")
            print(f"  Actual    : {actual}")
            verified = False
        else:
            print(f"Frame {i}: OK (matches)")

    # Also verify the anchor on frame 0 (optional)
    anchor_displayed = extract_hash_from_frame(frames[0], HASH_POS, HASH_FONT,
                                               HASH_SCALE, HASH_THICKNESS)
    expected_anchor = hashlib.sha256(b"FIRST_FRAME_ANCHOR").hexdigest()
    if anchor_displayed == expected_anchor:
        print("Frame 0 anchor hash OK")
    else:
        print("Frame 0 anchor hash MISMATCH (or OCR failed)")
        verified = False

    return verified

# ----------------------------------------------------------------------
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage:")
        print("  Encode:   python tiger_video.py encode input.mp4 output.mp4")
        print("  Verify:   python tiger_video.py verify encoded.mp4")
        sys.exit(1)

    mode = sys.argv[1]
    if mode == "encode":
        encode_video(sys.argv[2], sys.argv[3])
    elif mode == "verify":
        result = verify_video(sys.argv[2])
        print("\nVerification:", "PASSED" if result else "FAILED")
    else:
        print("Unknown mode. Use 'encode' or 'verify'.")