"""
Hash-then-Sign (Sender / Signer Side)
======================================================
Uses an RSA key pair, signs a message with the private key, and
sends message, and signature to receiver.py over TCP.

Two test scenarios are included:
  Scenario 1 original message + valid signature  -> receiver: ACCEPTED
  Scenario 2 tampered message + old signature    -> receiver: REJECTED

Run receiver.py first, then run this sender.py.
"""

import socket
import struct

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend

HOST = "127.0.0.1"
PORT = 65433


# Crypto helpers

# -----------------------------------------------------------------
#  PREFIXED KEYS  (private key lives on the sender side)
# -----------------------------------------------------------------

PRIVATE_KEY_PEM = b"""-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvXWPijwNG4W+j9kWm7edidQ0TyN8aSTb9gO0JXjHKAcxjOoJ
b2LvvystwK+F0pxa3PZ2YN9v4gv8AEf/v2J2RFJw/8oSYXSZ20sZJQk55259+ciV
tuUgxYlmmvZ6R3LUEQT8eLVCMw5R/rU3rt8m7FGkSlwYmSJ+YaWHYpO7UzaZq3/n
d3rw4kdscMtZkgc8ZUVGInxLp4rL+dEcLdpWeuf8ILtUGCvWpBYDGso5qkNtYYyC
3t9V7FsdqCXv7sHVACsxo3gbgnqwx9SVt1rfuByShiHRVCw+K5n9gkJBNM2iq/0n
276hVKpsNpaWWo7LlM4P/EsxIG/tvNkHWA+pbQIDAQABAoIBABnDUz4RIBfWg40h
uvJ426+C5OFqv2pi10Bod4ZC8BNliwY7oFspC9X7Q/ui7U0wSCYqTlBVAeyuBC9w
8E+bpDPH4X1/An8wcRLsxDdmPkgAx+5VVDqB2jInwYVKsA0S3yXjVNurNkclaDP9
JjHIPuLoJTGcEsI8JQ3CicWiC4PrOSqf5rLce5iF5Mb1gm3vTUCEF5WdbuCQuu2U
fnogZZlzMWyRw9cDqzi+ngdce0zg6GeuP+a72Wk6uF+MLRK/DVd1V83tC4FP+klA
51nzecBvKkQJE188JLFcLW1ga/lpNwoN6v6cPSYpVSUNDEQdbbkybD7ZHRKD+/dQ
AA4cgjcCgYEA/VpXfYXcscKqraoAIHOvVb8he6bSiPm2jie+l3hEEJ6Rx27x9fAK
P+k4jU/nfjpCBgqMy9ZUh2kmmWJCdfhAGTlxZPG8YhIeZBzNQefM8XUzIfoTl/M9
L1D00Lh5WlplI/Drko4CHKoK4/eVlyRArohKgyxQrL9R2cqG4lhk99sCgYEAv3BR
lUOycmYiciPkEIpCfq5WADfobRcjxiPJBsWP7DsZhq4asEG9dAlQshMHxP9nW6Gq
0+YGk9LG2lGBu7WEBjWd2yyH8+obIc6oV6Pn2eW30JTYbs8zzRJgiS23fk9q4Xac
hiK2dY4FtzOs80lF4OrS5bTTvokysHIwr//4qlcCgYEA1gqlL98P/P5BxEaDpOcX
kpHPb/AYnrCZkq/xTXbFymStNJh+wxDxF92pcXm4UeRWM1Rmby+8akpj2eIx1AjP
3n+xVV8FkLVOB7ZtkuiLNNOXR3VHaHCPmJIEwvGXVMZZ/GtmGoLvNdhTKxXYw8BV
BR8QFJIz9j4MoLuCCWg0pVECgYAp3impiGW1iUrNRbYyO9qxE/WxFIkbmqzzP45O
kdSZKI+7mcYyeB40C3l/iJPxbJ4xNxlCQJN9ruJYiuzhnaAjL4S3k3SkTrXiXe0J
RcqGKCxcTMFM8rn0hqlgNwRjdoD8kFFJMLxQ11++ommXx5WlzBWVsUeB60WPu1mU
lFnQ/wKBgCnO4eT1hKA9KSuOWKZ8oZpYOx/Bbm8+3L84Uy5UAFmPKuu6/skwhwU7
nkZe1v4L2ZuiLYoZlD2FZaA11XCM4PK3YlWc6U9h2kpCrZ2fkbeMYnf7LhNJaDwi
/9c0MMUcZSIYNahw/nP3JZY8U4qYC2kV1Etb6iWdUiftHcs9mfHT
-----END RSA PRIVATE KEY-----"""

PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXWPijwNG4W+j9kWm7ed
idQ0TyN8aSTb9gO0JXjHKAcxjOoJb2LvvystwK+F0pxa3PZ2YN9v4gv8AEf/v2J2
RFJw/8oSYXSZ20sZJQk55259+ciVtuUgxYlmmvZ6R3LUEQT8eLVCMw5R/rU3rt8m
7FGkSlwYmSJ+YaWHYpO7UzaZq3/nd3rw4kdscMtZkgc8ZUVGInxLp4rL+dEcLdpW
euf8ILtUGCvWpBYDGso5qkNtYYyC3t9V7FsdqCXv7sHVACsxo3gbgnqwx9SVt1rf
uByShiHRVCw+K5n9gkJBNM2iq/0n276hVKpsNpaWWo7LlM4P/EsxIG/tvNkHWA+p
bQIDAQAB
-----END PUBLIC KEY-----"""

private_key = serialization.load_pem_private_key(
    PRIVATE_KEY_PEM, password=None, backend=default_backend()
)


def compute_hash(message: bytes) -> str:
    """Return the hex-encoded SHA-256 digest of `message`.

    Hint: Use hashes.Hash(hashes.SHA256()) from the cryptography library.
          Call .update(message) then .finalize() to get the raw digest bytes,
          and convert to a hex string with .hex().
    """
    # TODO: Create a SHA-256 Hash object
    # TODO: Feed `message` into the hash object with .update()
    # TODO: Finalize and return the result as a hex string
    pass


def sign_message(private_key, message: bytes) -> bytes:
    """Sign `message` with RSA-PSS (SHA-256) and return the raw signature bytes.

    Hint: Call private_key.sign() with:
          - padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                        salt_length=padding.PSS.MAX_LENGTH)
          - hashes.SHA256() as the hash algorithm
    """
    # TODO: Sign `message` using the private key with RSA-PSS padding and SHA-256
    # TODO: Return the resulting signature bytes
    pass


# Sender logic

def sender(private_key, message: str):
    """Hash and sign the message. Print diagnostics and return (msg_bytes, signature).

    Steps:
      1. Encode the message string to bytes (UTF-8).
      2. Compute its SHA-256 hash using compute_hash().
      3. Sign the message bytes using sign_message().
      4. Print the original message, hash, and signature (hex).
      5. Return (msg_bytes, signature).
    """
    # TODO: Encode `message` to bytes using UTF-8
    # TODO: Compute the SHA-256 hash of msg_bytes using compute_hash()
    # TODO: Sign msg_bytes using sign_message()
    # TODO: Print the sender header, original message, hash, and signature hex
    # TODO: Return (msg_bytes, signature)
    pass


# Network layer

def send_frame(sock: socket.socket, data: bytes) -> None:
    """Send a 4-byte big-endian length prefix followed by `data`.

    Hint: Use struct.pack(">I", len(data)) to create the 4-byte header,
          then concatenate it with `data` and call sock.sendall().
    """
    # TODO: Pack len(data) as a 4-byte big-endian unsigned int
    # TODO: Send the length prefix + data in one sendall() call
    pass


def send_to_server(message: bytes, signature: bytes) -> None:
    """Open a TCP connection to (HOST, PORT) and transmit message and signature.

    Hint: Create a TCP socket, connect to (HOST, PORT), then call
          send_frame() twice: once for message, once for signature.
    """
    # TODO: Create a TCP socket (AF_INET, SOCK_STREAM)
    # TODO: Connect to (HOST, PORT)
    # TODO: Send the message as a length-prefixed frame using send_frame()
    # TODO: Send the signature as a length-prefixed frame using send_frame()
    # TODO: Print how many bytes were sent and to which address
    pass


# Test scenarios — DO NOT MODIFY

def scenario_1_valid_signature(private_key) -> None:
    print("\n==============================")
    print("Scenario 1: Valid Signature")
    print("==============================")
    message = "Approve payment of $500"
    msg_bytes, signature = sender(private_key, message)
    send_to_server(msg_bytes, signature)


def scenario_2_rejected_signature(private_key) -> None:
    print("\n==============================")
    print("Scenario 2: Rejected Signature")
    print("==============================")
    message = "Approve payment of $500"
    msg_bytes, signature = sender(private_key, message)

    # Tamper with the message *after* signing — signature no longer matches
    tampered_message = b"Approve payment of $900"
    print("Tampered message :", tampered_message.decode())
    send_to_server(tampered_message, signature)


def main() -> None:
    print("[Client] RSA-2048 key pair loaded.")

    print("\nSelect a scenario to run:")
    print("  1 -- Valid signature   (expected: ACCEPTED)")
    print("  2 -- Rejected signature (expected: REJECTED)")
    choice = input("Enter 1 or 2: ").strip()

    if choice == "1":
        scenario_1_valid_signature(private_key)
    elif choice == "2":
        scenario_2_rejected_signature(private_key)
    else:
        print("Invalid choice. Please enter 1 or 2.")


if __name__ == "__main__":
    main()
