#!/usr/bin/env python3
"""Aggregate credentials from @fatetraffic 4500 MIX archive.

Parses two formats found in victim directories:
1. "All Passwords.txt" / "Chrome/Default/Passwords.txt" — block format
2. "Events/BROWSER/logins_combined.json" — JSON array

Outputs deduplicated TSV: URL\tUSER\tPASSWORD\tSOURCE_DUMP

Usage:
  python3 aggregate_fatetraffic.py [extract_dir] [output.tsv]
"""

import json
import re
import sys
from pathlib import Path

PROJECT_DIR = Path(__file__).parent.parent
DEFAULT_EXTRACT_DIR = PROJECT_DIR / "findings" / "telegram_logs" / "boxed_pw"
DEFAULT_OUTPUT = PROJECT_DIR / "findings" / "data" / "fatetraffic_4500_creds.tsv"


def parse_block_format(text: str) -> list[tuple[str, str, str]]:
    """Parse Browser/Url/Login/Password block format."""
    results = []
    url = login = password = ""
    for line in text.splitlines():
        line = line.strip()
        if line.startswith("Url:"):
            url = line[4:].strip()
        elif line.startswith("Login:"):
            login = line[6:].strip()
        elif line.startswith("Password:"):
            password = line[9:].strip()
        elif line.startswith("Browser:") and url:
            if url and (login or password):
                results.append((url, login, password))
            url = login = password = ""
    if url and (login or password):
        results.append((url, login, password))
    return results


def parse_json_format(text: str) -> list[tuple[str, str, str]]:
    """Parse logins_combined.json format."""
    results = []
    try:
        data = json.loads(text)
        if isinstance(data, list):
            for entry in data:
                host = entry.get("host", "").strip()
                username = entry.get("username", "").strip()
                password = entry.get("password", "").strip()
                if host and (username or password):
                    results.append((host, username, password))
    except (json.JSONDecodeError, TypeError):
        pass
    return results


def extract_dump_id(dirname: str) -> str:
    """Extract short dump ID from directory name like [AE]_@FATETRAFFIC_2026_06_09_03378087."""
    parts = dirname.split("_")
    if len(parts) >= 5:
        return parts[-1]
    return dirname[:30]


def aggregate(extract_dir: Path, output: Path):
    all_creds: list[tuple[str, str, str, str]] = []
    seen = set()
    processed = 0
    skipped = 0
    total_victims = 0

    victim_dirs = sorted(
        [d for d in extract_dir.iterdir() if d.is_dir() and d.name.startswith("[")]
    )
    total_victims = len(victim_dirs)
    print(f"Victim directories: {total_victims}")

    for dump_dir in victim_dirs:
        dump_id = extract_dump_id(dump_dir.name)
        parsed = False

        all_pw = dump_dir / "All Passwords.txt"
        if all_pw.exists():
            try:
                text = all_pw.read_text(encoding="utf-8", errors="ignore")
                creds = parse_block_format(text)
                for url, login, pw in creds:
                    key = (url, login, pw)
                    if key not in seen:
                        seen.add(key)
                        all_creds.append((url, login, pw, dump_id))
                if creds:
                    parsed = True
            except Exception:
                pass

        if not parsed:
            logins_json = dump_dir / "Events" / "BROWSER" / "logins_combined.json"
            if logins_json.exists():
                try:
                    text = logins_json.read_text(encoding="utf-8", errors="ignore")
                    creds = parse_json_format(text)
                    for url, login, pw in creds:
                        key = (url, login, pw)
                        if key not in seen:
                            seen.add(key)
                            all_creds.append((url, login, pw, dump_id))
                    if creds:
                        parsed = True
                except Exception:
                    pass

        if not parsed:
            for browser_dir in dump_dir.iterdir():
                if browser_dir.is_dir() and browser_dir.name in ("Chrome", "Edge", "Brave-Browser", "Firefox", "Opera", "Opera GX", "Vivaldi", "Yandex"):
                    for profile_dir in browser_dir.iterdir():
                        if not profile_dir.is_dir():
                            continue
                        pw_file = profile_dir / "Passwords.txt"
                        if pw_file.exists():
                            try:
                                text = pw_file.read_text(encoding="utf-8", errors="ignore")
                                creds = parse_block_format(text)
                                for url, login, pw in creds:
                                    key = (url, login, pw)
                                    if key not in seen:
                                        seen.add(key)
                                        all_creds.append((url, login, pw, dump_id))
                                if creds:
                                    parsed = True
                            except Exception:
                                pass

        if parsed:
            processed += 1
        else:
            skipped += 1

    with open(output, "w", encoding="utf-8") as f:
        for url, login, pw, dump_id in all_creds:
            f.write(f"{url}\t{login}\t{pw}\t{dump_id}\n")

    print(f"Дампов с кредами: {processed}")
    print(f"Дампов без кредов: {skipped}")
    print(f"Уникальных кредов: {len(all_creds)}")
    print(f"Результат: {output}")


if __name__ == "__main__":
    extract_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_EXTRACT_DIR
    output = Path(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_OUTPUT

    if not extract_dir.is_dir():
        print(f"Error: {extract_dir} is not a directory")
        sys.exit(1)

    output.parent.mkdir(parents=True, exist_ok=True)
    aggregate(extract_dir, output)
