#!/usr/bin/env python3
"""Analyze credit_cards.json files from vault/.

Phase 4 — Digital Asset Impact.
BIN analysis, expiration check, network identification.
No full card numbers in output — masked to first 6 + last 4 (BIN + suffix).
"""

import json
import sys
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path

VAULT_DIR = Path("findings/vault")
NOW = datetime.now(timezone.utc)

BIN_RANGES = {
    "4": "Visa",
    "51": "Mastercard", "52": "Mastercard", "53": "Mastercard",
    "54": "Mastercard", "55": "Mastercard",
    "2221": "Mastercard", "2720": "Mastercard",
    "34": "Amex", "37": "Amex",
    "6011": "Discover", "65": "Discover",
    "62": "UnionPay",
    "35": "JCB",
    "30": "Diners Club", "36": "Diners Club", "38": "Diners Club",
    "50": "Maestro", "56": "Maestro", "57": "Maestro",
    "58": "Maestro", "63": "Maestro", "67": "Maestro",
}


def identify_network(card_number: str) -> str:
    clean = card_number.replace(" ", "").replace("-", "")
    for prefix_len in [4, 2, 1]:
        prefix = clean[:prefix_len]
        if prefix in BIN_RANGES:
            return BIN_RANGES[prefix]
    return "Unknown"


def mask_card(card_number: str) -> str:
    clean = card_number.replace(" ", "").replace("-", "")
    if len(clean) < 10:
        return "INVALID"
    return clean[:6] + "****" + clean[-4:]


def parse_expiration(exp_str: str) -> tuple:
    try:
        parts = exp_str.strip().split("/")
        if len(parts) == 2:
            month = int(parts[0])
            year = int(parts[1])
            if year < 100:
                year += 2000
            return month, year
    except (ValueError, IndexError):
        pass
    return None, None


def is_expired(month, year) -> bool:
    if month is None or year is None:
        return True
    exp_date = datetime(year, month, 1, tzinfo=timezone.utc)
    if month == 12:
        exp_date = datetime(year + 1, 1, 1, tzinfo=timezone.utc)
    else:
        exp_date = datetime(year, month + 1, 1, tzinfo=timezone.utc)
    return NOW >= exp_date


def luhn_check(card_number: str) -> bool:
    clean = card_number.replace(" ", "").replace("-", "")
    if not clean.isdigit() or len(clean) < 13:
        return False
    digits = [int(d) for d in clean]
    digits.reverse()
    total = 0
    for i, d in enumerate(digits):
        if i % 2 == 1:
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return total % 10 == 0


def main():
    files = sorted(VAULT_DIR.rglob("credit_cards.json"))

    all_cards = []
    for f in files:
        dump_id = f.parts[2] if len(f.parts) > 2 else "unknown"
        # Extract dump_id from path: findings/vault/DUMP_ID/...
        rel = f.relative_to(VAULT_DIR)
        dump_id = rel.parts[0]
        browser = rel.parts[1] if len(rel.parts) > 1 else ""
        browser += "/" + rel.parts[2] if len(rel.parts) > 2 else ""

        try:
            with open(f) as fh:
                cards = json.load(fh)
        except (json.JSONDecodeError, UnicodeDecodeError):
            continue

        if not isinstance(cards, list):
            continue

        for c in cards:
            if not isinstance(c, dict):
                continue
            num = c.get("card_number", "").strip()
            if not num or len(num.replace(" ", "").replace("-", "")) < 10:
                continue

            exp_str = c.get("expiration", "")
            month, year = parse_expiration(exp_str)
            network = identify_network(num)
            expired = is_expired(month, year)
            valid_luhn = luhn_check(num)

            all_cards.append({
                "dump_id": dump_id,
                "browser": browser,
                "masked": mask_card(num),
                "bin": num.replace(" ", "").replace("-", "")[:6],
                "network": network,
                "name_on_card": c.get("name_on_card", ""),
                "expiration": exp_str,
                "expired": expired,
                "luhn_valid": valid_luhn,
            })

    # === Output ===
    ts = NOW.strftime("%Y-%m-%dT%H:%M:%SZ")

    networks = Counter(c["network"] for c in all_cards)
    valid_cards = [c for c in all_cards if not c["expired"] and c["luhn_valid"]]
    expired_cards = [c for c in all_cards if c["expired"]]
    invalid_luhn = [c for c in all_cards if not c["luhn_valid"]]
    dumps_with_cards = set(c["dump_id"] for c in all_cards)
    dumps_with_valid = set(c["dump_id"] for c in valid_cards)

    print(f"# Credit Card Exposure Analysis — Phase 4")
    print(f"")
    print(f"**Timestamp:** {ts}")
    print(f"**Скрипт:** scripts/analyze_credit_cards.py")
    print(f"**Источник:** {len(files)} файлов credit_cards.json из vault/")
    print(f"")
    print(f"---")
    print(f"")
    print(f"## Общая статистика")
    print(f"")
    print(f"| Метрика | Значение |")
    print(f"|---------|----------|")
    print(f"| Файлов credit_cards.json | {len(files)} |")
    print(f"| Всего карт | {len(all_cards)} |")
    print(f"| Не истекших + Luhn valid | **{len(valid_cards)}** |")
    print(f"| Истекших | {len(expired_cards)} |")
    print(f"| Luhn invalid (возможно тестовые) | {len(invalid_luhn)} |")
    print(f"| Дампов с картами | {len(dumps_with_cards)} |")
    print(f"| Дампов с valid картами | {len(dumps_with_valid)} |")
    print(f"")

    print(f"## Платёжные сети")
    print(f"")
    print(f"| Сеть | Всего | Valid (not expired + Luhn) |")
    print(f"|------|:-----:|:-------------------------:|")
    for net, cnt in networks.most_common():
        valid_cnt = sum(1 for c in valid_cards if c["network"] == net)
        print(f"| {net} | {cnt} | {valid_cnt} |")
    print(f"")

    print(f"## Valid карты по дампам")
    print(f"")
    print(f"| Dump ID | Карт | Сети | Имена | Expiration range |")
    print(f"|---------|:----:|------|-------|-----------------|")
    for dump_id in sorted(dumps_with_valid):
        dc = [c for c in valid_cards if c["dump_id"] == dump_id]
        nets = ", ".join(sorted(set(c["network"] for c in dc)))
        names = ", ".join(sorted(set(c["name_on_card"] for c in dc if c["name_on_card"]))) or "—"
        exps = [c["expiration"] for c in dc if c["expiration"]]
        exp_range = f"{min(exps)}–{max(exps)}" if exps else "—"
        print(f"| {dump_id} | {len(dc)} | {nets} | {names} | {exp_range} |")
    print(f"")

    print(f"## Детализация (valid карты)")
    print(f"")
    print(f"| # | Dump ID | BIN (6) | Masked | Network | Name | Expiration | Luhn |")
    print(f"|---|---------|---------|--------|---------|------|:----------:|:----:|")
    for i, c in enumerate(valid_cards, 1):
        print(f"| {i} | {c['dump_id'][:30]} | {c['bin']} | {c['masked']} | {c['network']} | {c['name_on_card'] or '—'} | {c['expiration']} | {'✓' if c['luhn_valid'] else '✗'} |")
    print(f"")

    if expired_cards:
        print(f"## Expired карты (справочно)")
        print(f"")
        print(f"| # | Dump ID | BIN (6) | Network | Name | Expiration |")
        print(f"|---|---------|---------|---------|------|:----------:|")
        for i, c in enumerate(expired_cards, 1):
            print(f"| {i} | {c['dump_id'][:30]} | {c['bin']} | {c['network']} | {c['name_on_card'] or '—'} | {c['expiration']} |")
        print(f"")

    print(f"---")
    print(f"")
    print(f"*BIN = первые 6 цифр (Bank Identification Number). Полные номера маскированы (BIN + last 4). Закрывает пробел P4-2 (gap-analysis.md).*")


if __name__ == "__main__":
    main()
