#!/usr/bin/env python3
"""Analyze 491 cookie files: expiration, session validity, hijack feasibility scoring.

Per methodology.md Phase 3 — Session Exposure Analysis.
Per quality-criteria.md: "Session exposure documented with hijack feasibility score."

Output: TSV summary + markdown report.
"""

import csv
import json
import os
import sys
import re
from collections import Counter, defaultdict
from datetime import datetime, timezone
from pathlib import Path

VAULT_DIR = Path("findings/vault")
NOW_UNIX = datetime.now(timezone.utc).timestamp()
CHROME_EPOCH_OFFSET = 11644473600

HIGH_VALUE_DOMAINS = {
    "banking": [
        "chase.com", "wellsfargo.com", "bankofamerica.com", "usbank.com",
        "navyfederal.org", "citi.com", "capitalone.com", "ally.com",
        "paypal.com", "venmo.com", "blockchain.com", "coinbase.com",
        "binance.com", "kraken.com", "sberbank.ru", "alfabank.ru",
        "otpbank.hu", "santander.com", "lloydsbank.co.uk", "becu.org",
        "fidelity.com", "schwab.com", "tdameritrade.com",
    ],
    "email": [
        "mail.google.com", "outlook.live.com", "outlook.office.com",
        "login.microsoftonline.com", "mail.yahoo.com", "proton.me",
        "protonmail.com", "mail.ru", "e.mail.ru", "yandex.ru",
    ],
    "corporate": [
        "github.com", "gitlab.com", "bitbucket.org", "dev.azure.com",
        "docker.com", "hub.docker.com", "aws.amazon.com", "console.aws.amazon.com",
        "portal.azure.com", "cloud.google.com", "jenkins", "jira",
        "confluence", "slack.com", "teams.microsoft.com",
    ],
    "social": [
        "facebook.com", "instagram.com", "twitter.com", "x.com",
        "discord.com", "reddit.com", "linkedin.com", "tiktok.com",
        "vk.com", "telegram.org", "web.telegram.org",
    ],
    "gaming": [
        "roblox.com", "store.steampowered.com", "epicgames.com",
        "riot.com", "ea.com", "xbox.com", "playstation.com",
    ],
    "shopping": [
        "amazon.com", "ebay.com", "aliexpress.com", "walmart.com",
    ],
    "streaming": [
        "netflix.com", "spotify.com", "disneyplus.com", "hulu.com",
        "youtube.com", "twitch.tv",
    ],
}

AUTH_COOKIE_PATTERNS = [
    (r"^\.ROBLOSECURITY$", "roblox_auth", "HIGH"),
    (r"^(SID|SSID|HSID|APISID|SAPISID|OSID|__Secure-\dPSID)$", "google_auth", "HIGH"),
    (r"^(auth_token|ct0)$", "twitter_auth", "HIGH"),
    (r"^(c_user|xs|datr)$", "facebook_auth", "HIGH"),
    (r"^li_at$", "linkedin_auth", "HIGH"),
    (r"^(session_id|sessionid|connect\.sid|PHPSESSID|_session|sid)$", "generic_session", "HIGH"),
    (r"^(token|auth|access_token|refresh_token|jwt|bearer)$", "generic_auth", "HIGH"),
    (r"^__stripe_mid$", "stripe_session", "MEDIUM"),
    (r"^_gh_sess$", "github_session", "HIGH"),
    (r"^(_gitlab_session|remember_user_token)$", "gitlab_session", "HIGH"),
    (r"^(JSESSIONID|ASP\.NET_SessionId)$", "enterprise_session", "HIGH"),
    (r"^(wp-settings-\d+|wordpress_logged_in)$", "wordpress_auth", "MEDIUM"),
    (r"^user_session$", "github_user_session", "HIGH"),
    (r"^logged_in$", "generic_logged_in", "MEDIUM"),
]

COMPILED_PATTERNS = [(re.compile(p), label, severity) for p, label, severity in AUTH_COOKIE_PATTERNS]


def normalize_expiry(expires_val, browser_type):
    if expires_val is None or expires_val == 0 or expires_val == -1:
        return 0
    try:
        exp = float(expires_val)
    except (ValueError, TypeError):
        return 0

    if browser_type == "firefox":
        return exp / 1000.0
    else:
        if exp > 13000000000:
            return exp - CHROME_EPOCH_OFFSET
        elif exp > 1700000000:
            return exp
        else:
            return 0


def detect_browser(path_str):
    if "firefox" in path_str.lower():
        return "firefox"
    return "chromium"


def categorize_domain(domain):
    domain_clean = domain.lstrip(".")
    for category, domains in HIGH_VALUE_DOMAINS.items():
        for d in domains:
            if domain_clean == d or domain_clean.endswith("." + d):
                return category
    return "other"


def match_auth_cookie(name):
    for pattern, label, severity in COMPILED_PATTERNS:
        if pattern.match(name):
            return label, severity
    return None, None


def analyze_dump(dump_path):
    cookie_files = list(dump_path.rglob("cookies.json"))
    if not cookie_files:
        return None

    dump_id = dump_path.name
    result = {
        "dump_id": dump_id,
        "files": len(cookie_files),
        "total_cookies": 0,
        "valid_cookies": 0,
        "expired_cookies": 0,
        "session_cookies": 0,
        "auth_cookies_valid": 0,
        "auth_cookies_expired": 0,
        "domains": set(),
        "valid_domains": set(),
        "categories": Counter(),
        "valid_categories": Counter(),
        "auth_labels": Counter(),
        "auth_valid_labels": Counter(),
        "high_value_sessions": [],
        "hijack_score": 0,
        "latest_expiry": 0,
        "browsers": set(),
    }

    for cf in cookie_files:
        browser = detect_browser(str(cf))
        result["browsers"].add(browser)

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

        if not isinstance(cookies, list):
            continue

        for c in cookies:
            if not isinstance(c, dict):
                continue

            result["total_cookies"] += 1
            domain = c.get("domain", "")
            name = c.get("name", "")
            expires_raw = c.get("expires", 0)
            is_session = c.get("session", False)
            http_only = c.get("httpOnly", False)
            secure = c.get("secure", False)
            value = c.get("value", "")

            result["domains"].add(domain.lstrip("."))
            cat = categorize_domain(domain)
            result["categories"][cat] += 1

            if is_session or expires_raw == 0:
                result["session_cookies"] += 1
                is_valid = True
            else:
                exp_unix = normalize_expiry(expires_raw, browser)
                if exp_unix > NOW_UNIX:
                    result["valid_cookies"] += 1
                    result["valid_domains"].add(domain.lstrip("."))
                    result["valid_categories"][cat] += 1
                    is_valid = True
                    if exp_unix > result["latest_expiry"]:
                        result["latest_expiry"] = exp_unix
                else:
                    result["expired_cookies"] += 1
                    is_valid = False

            auth_label, auth_severity = match_auth_cookie(name)
            if auth_label:
                if is_valid and value:
                    result["auth_cookies_valid"] += 1
                    result["auth_valid_labels"][auth_label] += 1

                    score_add = 3 if auth_severity == "HIGH" else 1
                    if not http_only and not secure:
                        score_add += 2
                    if cat in ("banking", "corporate", "email"):
                        score_add += 3

                    result["hijack_score"] += score_add

                    if cat != "other" or auth_severity == "HIGH":
                        result["high_value_sessions"].append({
                            "domain": domain,
                            "name": name,
                            "category": cat,
                            "auth_label": auth_label,
                            "httpOnly": http_only,
                            "secure": secure,
                            "expires_unix": normalize_expiry(expires_raw, browser) if not is_session else 0,
                        })
                else:
                    result["auth_cookies_expired"] += 1
                    result["auth_labels"][auth_label] += 1

    return result


def hijack_tier(score):
    if score >= 20:
        return "CRITICAL"
    elif score >= 10:
        return "HIGH"
    elif score >= 5:
        return "MEDIUM"
    elif score > 0:
        return "LOW"
    return "NONE"


def main():
    dumps = sorted(VAULT_DIR.iterdir())
    dumps = [d for d in dumps if d.is_dir()]

    results = []
    for d in dumps:
        r = analyze_dump(d)
        if r:
            results.append(r)

    results.sort(key=lambda x: x["hijack_score"], reverse=True)

    # === TSV output ===
    tsv_path = "findings/data/cookie_analysis.tsv"
    with open(tsv_path, "w", newline="") as f:
        w = csv.writer(f, delimiter="\t")
        w.writerow([
            "dump_id", "files", "total_cookies", "valid", "expired", "session",
            "auth_valid", "auth_expired", "domains", "valid_domains",
            "hijack_score", "hijack_tier", "browsers",
            "banking", "email", "corporate", "social", "gaming",
        ])
        for r in results:
            w.writerow([
                r["dump_id"], r["files"], r["total_cookies"],
                r["valid_cookies"], r["expired_cookies"], r["session_cookies"],
                r["auth_cookies_valid"], r["auth_cookies_expired"],
                len(r["domains"]), len(r["valid_domains"]),
                r["hijack_score"], hijack_tier(r["hijack_score"]),
                "+".join(sorted(r["browsers"])),
                r["valid_categories"].get("banking", 0),
                r["valid_categories"].get("email", 0),
                r["valid_categories"].get("corporate", 0),
                r["valid_categories"].get("social", 0),
                r["valid_categories"].get("gaming", 0),
            ])

    # === Markdown report ===
    total_cookies = sum(r["total_cookies"] for r in results)
    total_valid = sum(r["valid_cookies"] for r in results)
    total_expired = sum(r["expired_cookies"] for r in results)
    total_session = sum(r["session_cookies"] for r in results)
    total_auth_valid = sum(r["auth_cookies_valid"] for r in results)
    total_auth_expired = sum(r["auth_cookies_expired"] for r in results)
    all_domains = set()
    all_valid_domains = set()
    for r in results:
        all_domains.update(r["domains"])
        all_valid_domains.update(r["valid_domains"])

    tier_counts = Counter(hijack_tier(r["hijack_score"]) for r in results)
    cat_totals = Counter()
    cat_valid_totals = Counter()
    auth_totals = Counter()
    for r in results:
        for k, v in r["categories"].items():
            cat_totals[k] += v
        for k, v in r["valid_categories"].items():
            cat_valid_totals[k] += v
        for k, v in r["auth_valid_labels"].items():
            auth_totals[k] += v

    print("# Cookie Session Analysis — Phase 3")
    print()
    print(f"**Timestamp:** {datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}")
    print(f"**Скрипт:** scripts/analyze_cookies.py")
    print(f"**Данные:** findings/data/cookie_analysis.tsv")
    print()
    print("---")
    print()
    print("## Общая статистика")
    print()
    print(f"| Метрика | Значение |")
    print(f"|---------|----------|")
    print(f"| Дампов с cookies | {len(results)} из 168 |")
    print(f"| Файлов cookies.json | {sum(r['files'] for r in results)} |")
    print(f"| Всего cookies | {total_cookies:,} |")
    print(f"| Не истекших (valid) | {total_valid:,} ({total_valid*100//max(total_cookies,1)}%) |")
    print(f"| Истекших (expired) | {total_expired:,} ({total_expired*100//max(total_cookies,1)}%) |")
    print(f"| Сессионных (no expiry) | {total_session:,} ({total_session*100//max(total_cookies,1)}%) |")
    print(f"| Auth-cookies (valid) | {total_auth_valid:,} |")
    print(f"| Auth-cookies (expired) | {total_auth_expired:,} |")
    print(f"| Уникальных доменов | {len(all_domains):,} |")
    print(f"| Доменов с valid cookies | {len(all_valid_domains):,} |")
    print()

    print("## Hijack Feasibility Scoring")
    print()
    print("Методика: auth-cookie × severity (HIGH=3, MEDIUM=1) + бонус за отсутствие httpOnly/secure (+2) + бонус за категорию banking/corporate/email (+3).")
    print()
    print(f"| Tier | Дампов | Score range |")
    print(f"|------|:------:|------------|")
    print(f"| CRITICAL (≥20) | {tier_counts.get('CRITICAL', 0)} | Множество валидных auth-сессий к high-value сервисам |")
    print(f"| HIGH (10-19) | {tier_counts.get('HIGH', 0)} | Несколько валидных auth-сессий |")
    print(f"| MEDIUM (5-9) | {tier_counts.get('MEDIUM', 0)} | Единичные auth-сессии |")
    print(f"| LOW (1-4) | {tier_counts.get('LOW', 0)} | Минимальные auth-сессии |")
    print(f"| NONE (0) | {tier_counts.get('NONE', 0)} | Нет валидных auth-cookies |")
    print()

    print("## Категории cookies (valid only)")
    print()
    print(f"| Категория | Всего cookies | Valid cookies |")
    print(f"|-----------|:------------:|:------------:|")
    for cat in ["banking", "email", "corporate", "social", "gaming", "shopping", "streaming", "other"]:
        print(f"| {cat} | {cat_totals.get(cat, 0):,} | {cat_valid_totals.get(cat, 0):,} |")
    print()

    print("## Auth-cookie типы (valid)")
    print()
    print(f"| Тип | Кол-во |")
    print(f"|-----|:------:|")
    for label, cnt in auth_totals.most_common():
        print(f"| {label} | {cnt} |")
    print()

    print("## Топ-20 дампов по hijack feasibility")
    print()
    print(f"| # | Dump ID | Score | Tier | Cookies (valid/total) | Auth valid | Banking | Email | Corporate | Social |")
    print(f"|---|---------|:-----:|------|:--------------------:|:----------:|:-------:|:-----:|:---------:|:------:|")
    for i, r in enumerate(results[:20]):
        total = r["total_cookies"]
        valid = r["valid_cookies"] + r["session_cookies"]
        print(f"| {i+1} | {r['dump_id'][:35]} | {r['hijack_score']} | {hijack_tier(r['hijack_score'])} | {valid}/{total} | {r['auth_cookies_valid']} | {r['valid_categories'].get('banking', 0)} | {r['valid_categories'].get('email', 0)} | {r['valid_categories'].get('corporate', 0)} | {r['valid_categories'].get('social', 0)} |")
    print()

    # High-value sessions detail for top dumps
    print("## Детализация high-value сессий (CRITICAL tier)")
    print()
    critical_dumps = [r for r in results if hijack_tier(r["hijack_score"]) == "CRITICAL"]
    for r in critical_dumps[:10]:
        print(f"### {r['dump_id']}")
        print()
        hvs = sorted(r["high_value_sessions"], key=lambda x: x["category"])
        if hvs:
            print(f"| Domain | Cookie | Category | Auth Type | httpOnly | secure | Expires |")
            print(f"|--------|--------|----------|-----------|:-------:|:------:|---------|")
            for s in hvs[:20]:
                exp_str = datetime.fromtimestamp(s["expires_unix"], tz=timezone.utc).strftime("%Y-%m-%d") if s["expires_unix"] > 0 else "session"
                print(f"| {s['domain']} | {s['name']} | {s['category']} | {s['auth_label']} | {s['httpOnly']} | {s['secure']} | {exp_str} |")
            if len(hvs) > 20:
                print(f"| ... | ... | ... | ... | ... | ... | +{len(hvs)-20} more |")
        print()

    print("---")
    print()
    print(f"*Анализ выполнен в рамках Phase 3 методологии (docs/methodology.md). Закрывает пробелы P3-1, P3-2 (gap-analysis.md).*")


if __name__ == "__main__":
    main()
