#!/usr/bin/env python3
"""
Lab 4 - Synthetic dataset generator for the data-inventory and DLP policy drill.

Generates a small CSV of fictitious retailer records with mixed-sensitivity
columns. All values are invented; no real personal data is used or required.

The payment_card_number column uses the Luhn algorithm to produce well-formed
(but non-issued) synthetic card numbers, giving the DLP detector something
real to match against.

Usage:
    python synthetic_dataset.py          # writes synthetic_customers.csv
    python synthetic_dataset.py --rows 200
"""

import csv
import random
import argparse
from datetime import date, timedelta

# ---------------------------------------------------------------------------
# Luhn-valid card number generator
# ---------------------------------------------------------------------------

def luhn_checksum(digits: list[int]) -> int:
    """Return the Luhn check digit for a list of digits (without the check digit)."""
    total = 0
    for i, d in enumerate(reversed(digits)):
        if i % 2 == 0:
            doubled = d * 2
            total += doubled - 9 if doubled > 9 else doubled
        else:
            total += d
    return (10 - (total % 10)) % 10


def synthetic_visa_pan() -> str:
    """Return a 16-digit Luhn-valid number with a fictional 4xxx prefix."""
    prefix = [4] + [random.randint(0, 9) for _ in range(14)]
    check = luhn_checksum(prefix)
    return "".join(str(d) for d in prefix + [check])


# ---------------------------------------------------------------------------
# Synthetic identity-document generators (plausible format, not real)
# ---------------------------------------------------------------------------

def synthetic_passport() -> str:
    """Irish-style passport: 2 letters + 7 digits."""
    letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    return (
        random.choice(letters)
        + random.choice(letters)
        + str(random.randint(1000000, 9999999))
    )


def synthetic_dob() -> str:
    """Random date of birth between 1950 and 2002."""
    start = date(1950, 1, 1)
    end = date(2002, 12, 31)
    delta = (end - start).days
    return (start + timedelta(days=random.randint(0, delta))).isoformat()


# ---------------------------------------------------------------------------
# Dataset generation
# ---------------------------------------------------------------------------

FIRST_NAMES = [
    "Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Hank",
    "Iris", "Jack", "Karen", "Leo", "Mia", "Noel", "Olivia", "Pat",
    "Quinn", "Rosa", "Sam", "Tara",
]
LAST_NAMES = [
    "Smith", "Jones", "Murphy", "O'Brien", "Walsh", "Ryan", "Byrne",
    "Kelly", "O'Sullivan", "Doyle", "McCarthy", "O'Connor", "Nolan",
    "Lynch", "Dunne", "Quinn", "Gallagher", "Daly", "Kennedy", "Brennan",
]
STREETS = [
    "1 Main St", "42 Oak Ave", "7 Elm Rd", "15 Castle Lane",
    "88 Harbour View", "3 Church St", "21 Green Park",
]
TOWNS = ["Dublin", "Cork", "Galway", "Limerick", "Waterford"]
DOMAINS = ["example.com", "testmail.ie", "fakepost.net", "labmail.org"]


def generate_row(customer_id: int) -> dict:
    first = random.choice(FIRST_NAMES)
    last = random.choice(LAST_NAMES)
    return {
        "customer_id":      customer_id,
        "full_name":        f"{first} {last}",
        "email":            f"{first.lower()}.{last.lower()}{customer_id}@{random.choice(DOMAINS)}",
        "date_of_birth":    synthetic_dob(),
        "postal_address":   f"{random.choice(STREETS)}, {random.choice(TOWNS)}, Ireland",
        "payment_card_number": synthetic_visa_pan(),
        "passport_number":  synthetic_passport(),
        "order_total":      round(random.uniform(9.99, 499.99), 2),
        "marketing_opt_in": random.choice(["Y", "N"]),
        "internal_notes":   random.choice([
            "VIP customer",
            "Flagged for review",
            "Loyalty tier: Gold",
            "",
            "First-time buyer",
        ]),
    }


COLUMNS = [
    "customer_id", "full_name", "email", "date_of_birth", "postal_address",
    "payment_card_number", "passport_number", "order_total",
    "marketing_opt_in", "internal_notes",
]


def main() -> None:
    parser = argparse.ArgumentParser(description="Generate synthetic retailer dataset for Lab 4.")
    parser.add_argument("--rows", type=int, default=50, help="Number of rows to generate (default: 50)")
    parser.add_argument("--output", default="synthetic_customers.csv", help="Output CSV filename")
    args = parser.parse_args()

    rows = [generate_row(i + 1001) for i in range(args.rows)]
    with open(args.output, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=COLUMNS)
        writer.writeheader()
        writer.writerows(rows)

    print(f"[+] Written {args.rows} synthetic rows to {args.output}")
    print("[+] All values are invented; no real personal data was used.")

    # Quick Luhn self-check on a sample
    sample_pan = rows[0]["payment_card_number"]
    digits = [int(d) for d in sample_pan]
    check_digit = digits[-1]
    computed = luhn_checksum(digits[:-1])
    assert computed == check_digit, "Luhn self-check failed"
    print(f"[+] Luhn self-check passed on sample PAN {sample_pan[:6]}**********{sample_pan[-2:]}")


if __name__ == "__main__":
    main()
