#!/usr/bin/env python3
"""
Finance Dashboard Generator

Generates a comprehensive HTML dashboard from transaction data with:
- Weekly Pulse view (status, all categories, watch list)
- Monthly Overview (progress, categories with trends, 80/20 analysis)
- Budget Reality Check (4-week hit rates, suggestions)
- Transactions list with filtering

Week starts on Monday (Mon-Sun).
"""

import json
import re
import hashlib
from datetime import datetime, timedelta
from collections import defaultdict
from typing import Dict, List, Tuple, Any

# Paths
BASE_PATH = '/Users/kimhansen/Desktop/03 Workspace/ceo-agents/chl-effectiveness/.claude'
CATEGORIES_PATH = f'{BASE_PATH}/context/merchant-categories.json'
TRANSACTIONS_PATH = f'{BASE_PATH}/data/transactions/transactions-log.json'
OUTPUT_PATH = f'{BASE_PATH}/data/transactions/finance-dashboard.html'
CSV_PATH = f'{BASE_PATH}/data/transactions/imports/NetBank Accounts Data.csv'

# Load configuration
with open(CATEGORIES_PATH, 'r') as f:
    config = json.load(f)

categories = config['categories']
mappings = config['mappings']

def categorize_transaction(description: str) -> Tuple[str, str]:
    """Match description against patterns and return category."""
    for mapping in mappings:
        pattern = mapping['pattern']
        try:
            if re.search(pattern, description, re.IGNORECASE):
                return mapping['category'], mapping.get('notes', '')
        except re.error:
            continue
    return 'other', 'Uncategorized'

def parse_csv_line(line: str) -> dict:
    """Parse a CommBank CSV line."""
    parts = []
    current = ''
    in_quotes = False

    for char in line:
        if char == '"':
            in_quotes = not in_quotes
        elif char == ',' and not in_quotes:
            parts.append(current.strip().strip('"'))
            current = ''
        else:
            current += char
    parts.append(current.strip().strip('"'))

    if len(parts) >= 3:
        date_str = parts[0]
        amount_str = parts[1].replace('+', '').replace(',', '')
        description = parts[2]

        try:
            amount = float(amount_str)
            date = datetime.strptime(date_str, '%d/%m/%Y')
            return {'date': date, 'amount': amount, 'description': description}
        except (ValueError, IndexError):
            return None
    return None

def load_transactions() -> List[dict]:
    """Load and parse transactions from CSV."""
    transactions = []

    try:
        with open(CSV_PATH, 'r') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                tx = parse_csv_line(line)
                if tx:
                    category, notes = categorize_transaction(tx['description'])
                    tx['category'] = category
                    tx['category_notes'] = notes
                    tx['expense_type'] = categories.get(category, {}).get('expense_type', 'variable')
                    transactions.append(tx)
    except FileNotFoundError:
        pass

    transactions.sort(key=lambda x: x['date'], reverse=True)
    return transactions

def get_week_bounds(date: datetime) -> Tuple[datetime, datetime]:
    """Get Monday-Sunday bounds for the week containing the given date."""
    # Monday is 0, Sunday is 6
    days_since_monday = date.weekday()
    monday = date - timedelta(days=days_since_monday)
    monday = monday.replace(hour=0, minute=0, second=0, microsecond=0)
    sunday = monday + timedelta(days=6, hours=23, minutes=59, seconds=59)
    return monday, sunday

def get_month_bounds(date: datetime) -> Tuple[datetime, datetime]:
    """Get first and last day of the month containing the given date."""
    first_day = date.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
    if date.month == 12:
        last_day = date.replace(year=date.year + 1, month=1, day=1) - timedelta(seconds=1)
    else:
        last_day = date.replace(month=date.month + 1, day=1) - timedelta(seconds=1)
    return first_day, last_day

def filter_transactions(transactions: List[dict], start: datetime, end: datetime) -> List[dict]:
    """Filter transactions within date range."""
    return [t for t in transactions if start <= t['date'] <= end]

def aggregate_by_category(transactions: List[dict], expense_types: List[str] = None) -> Dict[str, dict]:
    """Aggregate spending by category, optionally filtering by expense type."""
    by_category = defaultdict(lambda: {'total': 0, 'count': 0, 'transactions': []})

    for tx in transactions:
        if tx['amount'] >= 0:  # Skip income
            continue
        if expense_types and tx.get('expense_type') not in expense_types:
            continue

        cat = tx['category']
        by_category[cat]['total'] += abs(tx['amount'])
        by_category[cat]['count'] += 1
        by_category[cat]['transactions'].append(tx)

    return dict(by_category)

def calculate_week_data(transactions: List[dict], week_start: datetime, week_end: datetime) -> dict:
    """Calculate all weekly metrics."""
    week_txs = filter_transactions(transactions, week_start, week_end)

    # Variable spending
    variable_cats = [c for c, info in categories.items() if info.get('expense_type') == 'variable']
    variable_spending = aggregate_by_category(week_txs, ['variable'])
    variable_total = sum(d['total'] for d in variable_spending.values())

    # Subscriptions
    subscription_spending = aggregate_by_category(week_txs, ['fixed_monthly'])
    subscription_total = sum(d['total'] for d in subscription_spending.values())

    # Irregular expenses
    irregular_spending = aggregate_by_category(week_txs, ['fixed_irregular', 'one_time'])
    irregular_total = sum(d['total'] for d in irregular_spending.values())

    # Calculate weekly budget
    weekly_budget = sum(
        categories[c].get('weekly_budget', 0)
        for c in variable_cats
        if c in categories
    )

    return {
        'start': week_start,
        'end': week_end,
        'variable_spending': variable_spending,
        'variable_total': variable_total,
        'subscription_spending': subscription_spending,
        'subscription_total': subscription_total,
        'irregular_spending': irregular_spending,
        'irregular_total': irregular_total,
        'budget': weekly_budget,
        'variance': variable_total - weekly_budget,
        'percent': (variable_total / weekly_budget * 100) if weekly_budget > 0 else 0,
        'transactions': week_txs
    }

def calculate_month_data(transactions: List[dict], month_start: datetime, month_end: datetime) -> dict:
    """Calculate all monthly metrics."""
    month_txs = filter_transactions(transactions, month_start, month_end)

    # Variable spending
    variable_cats = [c for c, info in categories.items() if info.get('expense_type') == 'variable']
    variable_spending = aggregate_by_category(month_txs, ['variable'])
    variable_total = sum(d['total'] for d in variable_spending.values())

    # Calculate monthly budget (weekly * 4.33)
    monthly_budget = sum(
        categories[c].get('weekly_budget', 0) * 4.33
        for c in variable_cats
        if c in categories
    )

    # Calculate days elapsed and expected pace
    days_in_month = (month_end - month_start).days + 1
    days_elapsed = (min(datetime.now(), month_end) - month_start).days + 1
    expected_percent = (days_elapsed / days_in_month) * 100

    return {
        'start': month_start,
        'end': month_end,
        'variable_spending': variable_spending,
        'variable_total': variable_total,
        'budget': monthly_budget,
        'variance': variable_total - monthly_budget,
        'percent': (variable_total / monthly_budget * 100) if monthly_budget > 0 else 0,
        'days_elapsed': days_elapsed,
        'days_total': days_in_month,
        'expected_percent': expected_percent,
        'transactions': month_txs
    }

def calculate_last_4_weeks(transactions: List[dict]) -> List[dict]:
    """Calculate data for the last 4 complete weeks."""
    today = datetime.now()
    current_week_start, _ = get_week_bounds(today)

    weeks = []
    for i in range(4):
        week_start = current_week_start - timedelta(weeks=i)
        week_end = week_start + timedelta(days=6, hours=23, minutes=59, seconds=59)
        week_data = calculate_week_data(transactions, week_start, week_end)
        weeks.append(week_data)

    return weeks

def calculate_budget_hit_rates(weeks: List[dict]) -> Dict[str, dict]:
    """Calculate budget hit rate per category over last 4 weeks."""
    variable_cats = [c for c, info in categories.items() if info.get('expense_type') == 'variable']
    hit_rates = {}

    for cat in variable_cats:
        budget = categories[cat].get('weekly_budget', 0)
        if budget <= 0:
            continue

        hits = 0
        total_spent = 0
        weeks_with_data = 0

        for week in weeks:
            spent = week['variable_spending'].get(cat, {}).get('total', 0)
            total_spent += spent
            weeks_with_data += 1
            if spent <= budget:
                hits += 1

        avg_spent = total_spent / weeks_with_data if weeks_with_data > 0 else 0
        hit_rate = (hits / len(weeks)) * 100 if weeks else 0

        # Suggestion logic
        if hit_rate >= 75:
            suggestion = "Keep"
            suggestion_icon = "✓"
        elif hit_rate >= 50:
            suggestion = "Monitor"
            suggestion_icon = "→"
        else:
            # Suggest new budget based on average + 10%
            suggested_budget = int(avg_spent * 1.1)
            suggestion = f"${suggested_budget}?"
            suggestion_icon = "→"

        hit_rates[cat] = {
            'budget': budget,
            'avg_spent': avg_spent,
            'hit_rate': hit_rate,
            'hits': hits,
            'weeks': len(weeks),
            'suggestion': suggestion,
            'suggestion_icon': suggestion_icon
        }

    return hit_rates

def find_top_leaks(month_data: dict, transactions: List[dict]) -> List[dict]:
    """Find top spending leaks - merchants/patterns over budget."""
    leaks = []

    # Find Uber Eats specifically
    uber_eats = [t for t in month_data['transactions']
                 if 'UBER' in t['description'].upper() and 'EATS' in t['description'].upper()
                 and t['amount'] < 0]
    if uber_eats:
        total = sum(abs(t['amount']) for t in uber_eats)
        count = len(uber_eats)
        avg = total / count if count > 0 else 0
        dining_budget = categories.get('dining_out', {}).get('weekly_budget', 116) * 4.33
        pct_of_budget = (total / dining_budget * 100) if dining_budget > 0 else 0

        leaks.append({
            'name': 'Uber Eats',
            'total': total,
            'count': count,
            'avg': avg,
            'insight': f"{pct_of_budget:.0f}% of dining budget goes to delivery",
            'tip': f"Cutting to {max(1, count//2)} orders/month saves ${total/2:.0f}"
        })

    # Find categories over budget
    for cat, data in month_data['variable_spending'].items():
        weekly_budget = categories.get(cat, {}).get('weekly_budget', 0)
        monthly_budget = weekly_budget * 4.33
        if monthly_budget > 0 and data['total'] > monthly_budget:
            over_pct = ((data['total'] - monthly_budget) / monthly_budget) * 100
            if over_pct > 20 and cat != 'dining_out':  # Skip dining if Uber Eats is already highlighted
                leaks.append({
                    'name': cat.replace('_', ' ').title(),
                    'total': data['total'],
                    'count': data['count'],
                    'avg': data['total'] / data['count'] if data['count'] > 0 else 0,
                    'insight': f"{over_pct:.0f}% over budget",
                    'tip': f"Reducing by 20% saves ${data['total'] * 0.2:.0f}/month"
                })

    return sorted(leaks, key=lambda x: x['total'], reverse=True)[:3]

def calculate_month_trends(transactions: List[dict]) -> Dict[str, dict]:
    """Calculate this month vs last month trends."""
    today = datetime.now()
    this_month_start, this_month_end = get_month_bounds(today)
    last_month_start, last_month_end = get_month_bounds(today - timedelta(days=30))

    this_month = aggregate_by_category(
        filter_transactions(transactions, this_month_start, this_month_end),
        ['variable']
    )
    last_month = aggregate_by_category(
        filter_transactions(transactions, last_month_start, last_month_end),
        ['variable']
    )

    trends = {}
    for cat in set(list(this_month.keys()) + list(last_month.keys())):
        this_total = this_month.get(cat, {}).get('total', 0)
        last_total = last_month.get(cat, {}).get('total', 0)

        if last_total > 0:
            change_pct = ((this_total - last_total) / last_total) * 100
        else:
            change_pct = 100 if this_total > 0 else 0

        if abs(change_pct) < 10:
            trend = "stable"
            arrow = "→"
        elif change_pct > 0:
            trend = "up"
            arrow = "↑"
        else:
            trend = "down"
            arrow = "↓"

        trends[cat] = {
            'this_month': this_total,
            'last_month': last_total,
            'change_pct': change_pct,
            'trend': trend,
            'arrow': arrow
        }

    return trends

def get_status_color(percent: float) -> Tuple[str, str, str]:
    """Return status emoji, color class, and text based on budget percentage."""
    if percent <= 100:
        return "🟢", "green", "ON TRACK"
    elif percent <= 120:
        return "🟡", "yellow", "WATCH IT"
    else:
        return "🔴", "red", "OVER BUDGET"

def format_currency(amount: float) -> str:
    """Format amount as currency string."""
    return f"${amount:,.0f}"

def generate_progress_bar(percent: float, width: int = 16) -> str:
    """Generate ASCII progress bar."""
    filled = min(int(percent / 100 * width), width)
    empty = width - filled
    return "▓" * filled + "░" * empty

# Category display names
CATEGORY_NAMES = {
    'groceries': 'Groceries',
    'dining_out': 'Dining Out',
    'transport': 'Transport',
    'sports_fitness': 'Sports',
    'personal': 'Personal',
    'entertainment': 'Entertainment',
    'retail': 'Retail',
    'subscriptions': 'Subscriptions',
    'health': 'Health',
    'utilities': 'Utilities',
    'smilla': 'Smilla',
    'travel': 'Travel',
    'house': 'House',
    'business': 'Business',
    'moving': 'Moving',
    'other': 'Other',
}

def generate_html(transactions: List[dict]) -> str:
    """Generate the complete HTML dashboard."""
    today = datetime.now()

    # Calculate all data
    week_start, week_end = get_week_bounds(today)
    month_start, month_end = get_month_bounds(today)

    week_data = calculate_week_data(transactions, week_start, week_end)
    month_data = calculate_month_data(transactions, month_start, month_end)
    last_4_weeks = calculate_last_4_weeks(transactions)
    hit_rates = calculate_budget_hit_rates(last_4_weeks)
    trends = calculate_month_trends(transactions)
    top_leaks = find_top_leaks(month_data, transactions)

    # Status
    status_emoji, status_class, status_text = get_status_color(week_data['percent'])

    # Variable categories ordered
    variable_cats = ['groceries', 'dining_out', 'transport', 'sports_fitness',
                     'personal', 'entertainment', 'retail', 'other']

    html = f'''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Finance Dashboard</title>
    <style>
        :root {{
            --green: #22c55e;
            --yellow: #eab308;
            --red: #ef4444;
            --bg: #f8fafc;
            --card: #ffffff;
            --text: #1e293b;
            --muted: #64748b;
            --border: #e2e8f0;
        }}

        * {{ margin: 0; padding: 0; box-sizing: border-box; }}

        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: var(--bg);
            color: var(--text);
            padding: 24px;
            line-height: 1.5;
        }}

        .nav {{
            display: flex;
            gap: 8px;
            margin-bottom: 24px;
            background: var(--card);
            padding: 8px;
            border-radius: 12px;
            width: fit-content;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }}

        .nav-btn {{
            padding: 10px 20px;
            border: none;
            background: transparent;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            color: var(--muted);
            transition: all 0.2s;
        }}

        .nav-btn:hover {{ background: var(--bg); }}
        .nav-btn.active {{ background: var(--text); color: white; }}

        .page {{ display: none; }}
        .page.active {{ display: block; }}

        .header {{
            margin-bottom: 24px;
        }}

        .header h1 {{
            font-size: 24px;
            font-weight: 600;
            margin-bottom: 4px;
        }}

        .header p {{
            color: var(--muted);
            font-size: 14px;
        }}

        .grid {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            max-width: 1400px;
        }}

        .card {{
            background: var(--card);
            border-radius: 16px;
            padding: 24px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }}

        .card.span-2 {{
            grid-column: span 2;
        }}

        .card-title {{
            font-size: 14px;
            font-weight: 600;
            color: var(--muted);
            text-transform: uppercase;
            letter-spacing: 0.5px;
            margin-bottom: 16px;
        }}

        /* Hero Status Card */
        .hero {{
            text-align: center;
            padding: 32px;
        }}

        .hero .status-emoji {{
            font-size: 48px;
            margin-bottom: 8px;
        }}

        .hero .status-text {{
            font-size: 24px;
            font-weight: 700;
            margin-bottom: 16px;
        }}

        .hero .status-text.green {{ color: var(--green); }}
        .hero .status-text.yellow {{ color: var(--yellow); }}
        .hero .status-text.red {{ color: var(--red); }}

        .hero .amount {{
            font-size: 36px;
            font-weight: 700;
            margin-bottom: 4px;
        }}

        .hero .subtext {{
            color: var(--muted);
            font-size: 14px;
        }}

        .hero .progress-container {{
            margin: 20px 0;
            height: 12px;
            background: var(--border);
            border-radius: 6px;
            overflow: hidden;
        }}

        .hero .progress-bar {{
            height: 100%;
            border-radius: 6px;
            transition: width 0.3s;
        }}

        .hero .progress-bar.green {{ background: var(--green); }}
        .hero .progress-bar.yellow {{ background: var(--yellow); }}
        .hero .progress-bar.red {{ background: var(--red); }}

        .hero .variance {{
            font-size: 18px;
            font-weight: 600;
            margin-top: 8px;
        }}

        .hero .variance.positive {{ color: var(--green); }}
        .hero .variance.negative {{ color: var(--red); }}

        /* Category Rows */
        .category-row {{
            display: flex;
            align-items: center;
            padding: 12px 0;
            border-bottom: 1px solid var(--border);
        }}

        .category-row:last-child {{
            border-bottom: none;
        }}

        .category-row.total {{
            border-top: 2px solid var(--border);
            margin-top: 8px;
            padding-top: 16px;
            font-weight: 600;
        }}

        .category-name {{
            width: 100px;
            font-size: 14px;
        }}

        .category-bar {{
            flex: 1;
            height: 8px;
            background: var(--border);
            border-radius: 4px;
            margin: 0 16px;
            overflow: hidden;
        }}

        .category-bar-fill {{
            height: 100%;
            border-radius: 4px;
            transition: width 0.3s;
        }}

        .category-bar-fill.under {{ background: var(--green); }}
        .category-bar-fill.over {{ background: var(--red); }}

        .category-values {{
            width: 120px;
            text-align: right;
            font-size: 13px;
        }}

        .category-status {{
            width: 30px;
            text-align: center;
        }}

        /* Watch List */
        .watch-item {{
            padding: 16px;
            background: #fef2f2;
            border-radius: 12px;
            margin-bottom: 12px;
            border-left: 4px solid var(--red);
        }}

        .watch-item:last-child {{
            margin-bottom: 0;
        }}

        .watch-title {{
            font-weight: 600;
            margin-bottom: 4px;
        }}

        .watch-detail {{
            font-size: 13px;
            color: var(--muted);
            margin-bottom: 8px;
        }}

        .watch-tip {{
            font-size: 13px;
            color: var(--green);
        }}

        /* Subscriptions & Irregular */
        .expense-list {{
            font-size: 14px;
        }}

        .expense-item {{
            display: flex;
            justify-content: space-between;
            padding: 8px 0;
            border-bottom: 1px solid var(--border);
        }}

        .expense-item:last-child {{
            border-bottom: none;
        }}

        .expense-total {{
            margin-top: 12px;
            padding-top: 12px;
            border-top: 1px solid var(--border);
            display: flex;
            justify-content: space-between;
            font-weight: 600;
        }}

        /* Month Progress */
        .month-stats {{
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 16px;
            margin-top: 16px;
        }}

        .stat {{
            text-align: center;
            padding: 16px;
            background: var(--bg);
            border-radius: 12px;
        }}

        .stat-value {{
            font-size: 24px;
            font-weight: 700;
        }}

        .stat-label {{
            font-size: 12px;
            color: var(--muted);
        }}

        /* Trend indicator */
        .trend {{
            font-size: 12px;
            padding: 2px 6px;
            border-radius: 4px;
        }}

        .trend.up {{ background: #fee2e2; color: var(--red); }}
        .trend.down {{ background: #dcfce7; color: var(--green); }}
        .trend.stable {{ background: var(--border); color: var(--muted); }}

        /* Leaks */
        .leak-item {{
            padding: 16px;
            background: var(--bg);
            border-radius: 12px;
            margin-bottom: 12px;
        }}

        .leak-header {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
        }}

        .leak-name {{
            font-weight: 600;
        }}

        .leak-amount {{
            font-weight: 600;
            color: var(--red);
        }}

        .leak-insight {{
            font-size: 13px;
            color: var(--muted);
            margin-bottom: 4px;
        }}

        .leak-tip {{
            font-size: 13px;
            color: var(--green);
        }}

        /* Budget Reality */
        .reality-row {{
            display: grid;
            grid-template-columns: 100px 60px 80px 80px 80px 40px;
            padding: 10px 0;
            border-bottom: 1px solid var(--border);
            font-size: 13px;
        }}

        .reality-header {{
            font-weight: 600;
            color: var(--muted);
        }}

        .reality-suggestion {{
            color: var(--green);
        }}

        .insight-box {{
            margin-top: 16px;
            padding: 16px;
            background: #fef9c3;
            border-radius: 12px;
            font-size: 14px;
        }}

        /* Transactions */
        .filters {{
            display: flex;
            gap: 8px;
            margin-bottom: 16px;
            flex-wrap: wrap;
        }}

        .filter-btn {{
            padding: 8px 16px;
            border: 1px solid var(--border);
            border-radius: 8px;
            background: white;
            font-size: 13px;
            cursor: pointer;
        }}

        .filter-btn.active {{
            background: var(--text);
            color: white;
            border-color: var(--text);
        }}

        .tx-list {{
            max-height: 500px;
            overflow-y: auto;
        }}

        .tx-row {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 12px 0;
            border-bottom: 1px solid var(--border);
        }}

        .tx-info {{
            flex: 1;
        }}

        .tx-desc {{
            font-size: 14px;
            margin-bottom: 2px;
        }}

        .tx-meta {{
            font-size: 12px;
            color: var(--muted);
        }}

        .tx-cat {{
            display: inline-block;
            padding: 2px 8px;
            background: var(--bg);
            border-radius: 4px;
            margin-left: 8px;
        }}

        .tx-amount {{
            font-weight: 600;
            font-size: 14px;
        }}

        .tx-amount.expense {{ color: var(--red); }}
        .tx-amount.income {{ color: var(--green); }}

        @media (max-width: 900px) {{
            .grid {{ grid-template-columns: 1fr; }}
            .card.span-2 {{ grid-column: span 1; }}
        }}
    </style>
</head>
<body>
    <div class="nav">
        <button class="nav-btn active" onclick="showPage('weekly')">Weekly</button>
        <button class="nav-btn" onclick="showPage('monthly')">Monthly</button>
        <button class="nav-btn" onclick="showPage('budget')">Budget Check</button>
        <button class="nav-btn" onclick="showPage('transactions')">Transactions</button>
    </div>

    <!-- WEEKLY PAGE -->
    <div id="page-weekly" class="page active">
        <div class="header">
            <h1>Weekly Pulse</h1>
            <p>{week_start.strftime('%b %d')} - {week_end.strftime('%b %d, %Y')}</p>
        </div>

        <div class="grid">
            <!-- Hero Status -->
            <div class="card hero">
                <div class="status-emoji">{status_emoji}</div>
                <div class="status-text {status_class}">{status_text}</div>
                <div class="amount">{format_currency(week_data['variable_total'])}</div>
                <div class="subtext">of {format_currency(week_data['budget'])} weekly budget</div>
                <div class="progress-container">
                    <div class="progress-bar {status_class}" style="width: {min(week_data['percent'], 100)}%"></div>
                </div>
                <div class="variance {'positive' if week_data['variance'] < 0 else 'negative'}">
                    {'$' + format_currency(abs(week_data['variance']))[1:]} {'under' if week_data['variance'] < 0 else 'over'} budget
                </div>
            </div>

            <!-- Category Breakdown -->
            <div class="card">
                <div class="card-title">Variable Spending by Category</div>
'''

    # Add category rows
    for cat in variable_cats:
        info = categories.get(cat, {})
        budget = info.get('weekly_budget', 0)
        spent = week_data['variable_spending'].get(cat, {}).get('total', 0)
        pct = (spent / budget * 100) if budget > 0 else 0
        is_over = spent > budget

        html += f'''
                <div class="category-row">
                    <div class="category-name">{CATEGORY_NAMES.get(cat, cat)}</div>
                    <div class="category-bar">
                        <div class="category-bar-fill {'over' if is_over else 'under'}" style="width: {min(pct, 100)}%"></div>
                    </div>
                    <div class="category-values">{format_currency(spent)} / {format_currency(budget)}</div>
                    <div class="category-status">{'⚠️' if is_over else '✓'}</div>
                </div>
'''

    # Total row
    html += f'''
                <div class="category-row total">
                    <div class="category-name">TOTAL</div>
                    <div class="category-bar">
                        <div class="category-bar-fill {'over' if week_data['variance'] > 0 else 'under'}" style="width: {min(week_data['percent'], 100)}%"></div>
                    </div>
                    <div class="category-values">{format_currency(week_data['variable_total'])} / {format_currency(week_data['budget'])}</div>
                    <div class="category-status">{'⚠️' if week_data['variance'] > 0 else '✓'}</div>
                </div>
            </div>
'''

    # Watch List
    watch_cats = [(cat, week_data['variable_spending'].get(cat, {}).get('total', 0))
                  for cat in variable_cats
                  if week_data['variable_spending'].get(cat, {}).get('total', 0) > categories.get(cat, {}).get('weekly_budget', 0)]

    html += '''
            <!-- Watch List -->
            <div class="card">
                <div class="card-title">⚠️ Watch This Week</div>
'''

    if watch_cats:
        for cat, spent in sorted(watch_cats, key=lambda x: x[1], reverse=True)[:2]:
            budget = categories.get(cat, {}).get('weekly_budget', 0)
            pct = (spent / budget * 100) if budget > 0 else 0

            # Find top merchant in category
            cat_txs = week_data['variable_spending'].get(cat, {}).get('transactions', [])
            merchants = defaultdict(lambda: {'total': 0, 'count': 0})
            for tx in cat_txs:
                # Simplify merchant name
                desc = tx['description'].split()[0:3]
                merchant = ' '.join(desc)
                merchants[merchant]['total'] += abs(tx['amount'])
                merchants[merchant]['count'] += 1

            top_merchant = max(merchants.items(), key=lambda x: x[1]['total']) if merchants else None

            html += f'''
                <div class="watch-item">
                    <div class="watch-title">{CATEGORY_NAMES.get(cat, cat)}: {pct:.0f}% of budget</div>
                    <div class="watch-detail">
                        {format_currency(spent)} spent vs {format_currency(budget)} budget
                    </div>
'''
            if top_merchant:
                html += f'''
                    <div class="watch-detail">
                        Top: {top_merchant[0][:25]} ({top_merchant[1]['count']} orders, {format_currency(top_merchant[1]['total'])})
                    </div>
'''
            html += '''
                </div>
'''
    else:
        html += '''
                <div style="padding: 20px; text-align: center; color: var(--green);">
                    ✓ All categories on track this week!
                </div>
'''

    html += '''
            </div>
'''

    # Subscriptions
    html += '''
            <!-- Subscriptions -->
            <div class="card">
                <div class="card-title">📦 Subscriptions This Week</div>
                <div class="expense-list">
'''

    if week_data['subscription_spending']:
        for cat, data in week_data['subscription_spending'].items():
            for tx in data['transactions'][:5]:
                desc = tx['description'][:35]
                html += f'''
                    <div class="expense-item">
                        <span>{desc}</span>
                        <span>{format_currency(abs(tx['amount']))}</span>
                    </div>
'''
        html += f'''
                    <div class="expense-total">
                        <span>Total</span>
                        <span>{format_currency(week_data['subscription_total'])}</span>
                    </div>
'''
    else:
        html += '''
                    <div style="color: var(--muted); padding: 10px 0;">No subscription charges this week</div>
'''

    html += '''
                </div>
            </div>
'''

    # Irregular Expenses
    html += '''
            <!-- Irregular Expenses -->
            <div class="card">
                <div class="card-title">📋 Irregular This Week</div>
                <div class="expense-list">
'''

    if week_data['irregular_spending']:
        for cat, data in week_data['irregular_spending'].items():
            for tx in data['transactions'][:5]:
                desc = tx['description'][:35]
                html += f'''
                    <div class="expense-item">
                        <span>{CATEGORY_NAMES.get(cat, cat)}: {desc}</span>
                        <span>{format_currency(abs(tx['amount']))}</span>
                    </div>
'''
        html += f'''
                    <div class="expense-total">
                        <span>Total (excluded from variable)</span>
                        <span>{format_currency(week_data['irregular_total'])}</span>
                    </div>
'''
    else:
        html += '''
                    <div style="color: var(--muted); padding: 10px 0;">No irregular expenses this week</div>
'''

    html += '''
                </div>
            </div>
        </div>
    </div>

    <!-- MONTHLY PAGE -->
    <div id="page-monthly" class="page">
        <div class="header">
            <h1>Monthly Overview</h1>
            <p>''' + month_start.strftime('%B %Y') + '''</p>
        </div>

        <div class="grid">
            <!-- Month Progress -->
            <div class="card">
                <div class="card-title">Month Progress</div>
                <div class="amount" style="font-size: 28px; margin-bottom: 8px;">''' + format_currency(month_data['variable_total']) + '''</div>
                <div class="subtext">of ''' + format_currency(month_data['budget']) + ''' monthly budget</div>
                <div class="progress-container" style="margin: 16px 0; height: 12px; background: var(--border); border-radius: 6px;">
                    <div style="width: ''' + str(min(month_data['percent'], 100)) + '''%; height: 100%; background: ''' + ('var(--green)' if month_data['percent'] <= 100 else 'var(--red)') + '''; border-radius: 6px;"></div>
                </div>
                <div style="font-size: 14px; color: var(--muted);">
                    Day ''' + str(month_data['days_elapsed']) + ''' of ''' + str(month_data['days_total']) + ''' •
                    ''' + ('On pace' if month_data['percent'] <= month_data['expected_percent'] + 5 else 'Ahead of pace') + '''
                    (expect ''' + f"{month_data['expected_percent']:.0f}" + '''% by now)
                </div>

                <div class="month-stats">
                    <div class="stat">
                        <div class="stat-value">''' + str(len([t for t in month_data['transactions'] if t['amount'] < 0])) + '''</div>
                        <div class="stat-label">Transactions</div>
                    </div>
                    <div class="stat">
                        <div class="stat-value">''' + format_currency(month_data['variable_total'] / max(month_data['days_elapsed'], 1)) + '''</div>
                        <div class="stat-label">Daily Avg</div>
                    </div>
                    <div class="stat">
                        <div class="stat-value">''' + str(len([t for t in month_data['transactions'] if 'UBER' in t['description'].upper() and 'EATS' in t['description'].upper()])) + '''</div>
                        <div class="stat-label">Uber Eats</div>
                    </div>
                </div>
            </div>

            <!-- Top Leaks -->
            <div class="card">
                <div class="card-title">🔥 Top Spending Leaks</div>
'''

    if top_leaks:
        for i, leak in enumerate(top_leaks[:2], 1):
            html += f'''
                <div class="leak-item">
                    <div class="leak-header">
                        <span class="leak-name">{i}. {leak['name']}</span>
                        <span class="leak-amount">{format_currency(leak['total'])}</span>
                    </div>
                    <div class="leak-insight">{leak['insight']}</div>
                    <div class="leak-tip">💡 {leak['tip']}</div>
                </div>
'''
    else:
        html += '''
                <div style="padding: 20px; text-align: center; color: var(--green);">
                    ✓ No major spending leaks detected!
                </div>
'''

    html += '''
            </div>

            <!-- Monthly Categories -->
            <div class="card span-2">
                <div class="card-title">Monthly Spending by Category</div>
'''

    # Add monthly category rows with trends
    for cat in variable_cats:
        info = categories.get(cat, {})
        weekly_budget = info.get('weekly_budget', 0)
        monthly_budget = weekly_budget * 4.33
        spent = month_data['variable_spending'].get(cat, {}).get('total', 0)
        pct = (spent / monthly_budget * 100) if monthly_budget > 0 else 0
        is_over = spent > monthly_budget

        trend_info = trends.get(cat, {'arrow': '→', 'change_pct': 0, 'trend': 'stable'})

        html += f'''
                <div class="category-row">
                    <div class="category-name">{CATEGORY_NAMES.get(cat, cat)}</div>
                    <div class="category-bar">
                        <div class="category-bar-fill {'over' if is_over else 'under'}" style="width: {min(pct, 100)}%"></div>
                    </div>
                    <div class="category-values">{format_currency(spent)} / {format_currency(monthly_budget)}</div>
                    <div class="category-status">
                        <span class="trend {trend_info['trend']}">{trend_info['arrow']} {abs(trend_info['change_pct']):.0f}%</span>
                    </div>
                </div>
'''

    html += '''
            </div>
        </div>
    </div>

    <!-- BUDGET CHECK PAGE -->
    <div id="page-budget" class="page">
        <div class="header">
            <h1>Budget Reality Check</h1>
            <p>Last 4 weeks analysis</p>
        </div>

        <div class="grid">
            <div class="card span-2">
                <div class="card-title">Budget Accuracy</div>
                <div class="reality-row reality-header">
                    <div>Category</div>
                    <div>Hit %</div>
                    <div>Avg Spent</div>
                    <div>Budget</div>
                    <div>Suggested</div>
                    <div></div>
                </div>
'''

    for cat in variable_cats:
        if cat in hit_rates:
            hr = hit_rates[cat]
            html += f'''
                <div class="reality-row">
                    <div>{CATEGORY_NAMES.get(cat, cat)}</div>
                    <div>{hr['hit_rate']:.0f}%</div>
                    <div>{format_currency(hr['avg_spent'])}</div>
                    <div>{format_currency(hr['budget'])}</div>
                    <div class="reality-suggestion">{hr['suggestion']}</div>
                    <div>{hr['suggestion_icon']}</div>
                </div>
'''

    # Find problematic categories
    problem_cats = [cat for cat, hr in hit_rates.items() if hr['hit_rate'] < 50]
    if problem_cats:
        html += f'''
                <div class="insight-box">
                    💡 <strong>INSIGHT:</strong> Your {', '.join([CATEGORY_NAMES.get(c, c) for c in problem_cats])}
                    budget{'s are' if len(problem_cats) > 1 else ' is'} unrealistic.
                    Consider raising the budget or finding ways to reduce spending.
                </div>
'''

    html += '''
            </div>
        </div>
    </div>

    <!-- TRANSACTIONS PAGE -->
    <div id="page-transactions" class="page">
        <div class="header">
            <h1>Transactions</h1>
            <p>Recent spending</p>
        </div>

        <div class="grid">
            <div class="card span-2">
                <div class="filters">
                    <button class="filter-btn active" onclick="filterTx('all')">All</button>
                    <button class="filter-btn" onclick="filterTx('groceries')">Groceries</button>
                    <button class="filter-btn" onclick="filterTx('dining_out')">Dining</button>
                    <button class="filter-btn" onclick="filterTx('transport')">Transport</button>
                    <button class="filter-btn" onclick="filterTx('subscriptions')">Subscriptions</button>
                    <button class="filter-btn" onclick="filterTx('retail')">Retail</button>
                </div>

                <div class="tx-list">
'''

    # Add recent transactions
    for tx in transactions[:100]:
        if tx['amount'] >= 0 and tx['category'] == 'income':
            continue  # Skip income

        desc = tx['description'][:50]
        amount_class = 'expense' if tx['amount'] < 0 else 'income'
        prefix = '-' if tx['amount'] < 0 else '+'

        html += f'''
                    <div class="tx-row" data-category="{tx['category']}">
                        <div class="tx-info">
                            <div class="tx-desc">{desc}</div>
                            <div class="tx-meta">
                                {tx['date'].strftime('%b %d')}
                                <span class="tx-cat">{CATEGORY_NAMES.get(tx['category'], tx['category'])}</span>
                            </div>
                        </div>
                        <div class="tx-amount {amount_class}">{prefix}{format_currency(abs(tx['amount']))}</div>
                    </div>
'''

    html += '''
                </div>
            </div>
        </div>
    </div>

    <script>
        function showPage(page) {
            document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
            document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
            document.getElementById('page-' + page).classList.add('active');
            event.target.classList.add('active');
        }

        function filterTx(category) {
            document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
            event.target.classList.add('active');

            document.querySelectorAll('.tx-row').forEach(row => {
                if (category === 'all' || row.dataset.category === category) {
                    row.style.display = 'flex';
                } else {
                    row.style.display = 'none';
                }
            });
        }
    </script>
</body>
</html>
'''

    return html

def main():
    """Main entry point."""
    print("Loading transactions...")
    transactions = load_transactions()
    print(f"Loaded {len(transactions)} transactions")

    print("Generating dashboard...")
    html = generate_html(transactions)

    print(f"Writing to {OUTPUT_PATH}...")
    with open(OUTPUT_PATH, 'w') as f:
        f.write(html)

    print("Done!")

    # Print quick summary
    today = datetime.now()
    week_start, week_end = get_week_bounds(today)
    week_data = calculate_week_data(transactions, week_start, week_end)

    status_emoji, _, status_text = get_status_color(week_data['percent'])

    print(f"\n{status_emoji} {status_text}")
    print(f"This week: ${week_data['variable_total']:,.0f} / ${week_data['budget']:,.0f} ({week_data['percent']:.0f}%)")
    print(f"Variance: {'$' + str(abs(int(week_data['variance'])))} {'under' if week_data['variance'] < 0 else 'over'}")

if __name__ == '__main__':
    main()
