#!/usr/bin/env python3
"""
Atlas Intelligence Engine
Runs on atlas-01. Pulls Odoo invoices, runs Monte Carlo H2 forecast,
outputs /opt/atlas/setup/intelligence.json (served at /feeds/intelligence.json).
Schedule: cron every hour or on-demand.
"""

import json, os, random, sys, xmlrpc.client
from datetime import datetime, timezone
from urllib.request import urlopen, Request
from urllib.error import URLError

ODOO_URL  = 'http://<TAILSCALE_IP>:8069'
ODOO_DB   = 'atlas'
ODOO_USER = 'chaib@atlascorporation.nl'
ODOO_PASS = os.environ['ODOO_PASS']
OUT_PATH  = '/opt/atlas/setup/intelligence.json'

# ── Verified 2025 actuals (from Odoo export + bank reconciliation) ────────────
ACTUALS_2025 = {
    '2025-01': 237, '2025-02': 225, '2025-03': 0,   '2025-04': 255,
    '2025-05': 1625,'2025-06': 533, '2025-07': 660,  '2025-08': -200,
    '2025-09': 1300,'2025-10': 1400,'2025-11': 280,  '2025-12': 200,
}
ACTUALS_2026_FALLBACK = {
    '2026-01': 68,  '2026-02': 977, '2026-03': 4206,
    '2026-04': 1479,'2026-05': 1627,'2026-06': 371,
}

def fetch_odoo():
    """Pull posted invoices from Odoo via XMLRPC, return {YYYY-MM: amount} dicts."""
    try:
        common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
        uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASS, {})
        if not uid:
            return None, None

        models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')
        moves = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
            'account.move', 'search_read',
            [[['move_type','in',['out_invoice','out_refund']],
              ['state','=','posted'],
              ['invoice_date','>=','2025-01-01']]],
            {'fields':['invoice_date','amount_untaxed','move_type'],
             'limit':500}
        )
        if not moves:
            return None, None

        by_month_2025, by_month_2026 = {}, {}
        for m in moves:
            if not m.get('invoice_date'):
                continue
            ym = m['invoice_date'][:7]
            amt = m['amount_untaxed'] * (-1 if m['move_type'] == 'out_refund' else 1)
            if ym.startswith('2025'):
                by_month_2025[ym] = by_month_2025.get(ym, 0) + amt
            elif ym.startswith('2026'):
                by_month_2026[ym] = by_month_2026.get(ym, 0) + amt

        return (
            {k: round(v) for k,v in by_month_2025.items()} or None,
            {k: round(v) for k,v in by_month_2026.items()} or None,
        )
    except Exception as e:
        print(f'[intelligence] Odoo pull failed: {e}', file=sys.stderr)
        return None, None

def monte_carlo(actuals_2026, n=10000):
    """Project H2 2026 (Jul–Dec) from recent velocity + POS MRR growth."""
    # Base: last 3 completed months (exclude partial current month)
    months_sorted = sorted(k for k in actuals_2026 if actuals_2026[k] > 0)
    recent = [actuals_2026[k] for k in months_sorted[-3:]] if months_sorted else [1500]
    base_mean = sum(recent) / len(recent)
    base_std  = max(base_mean * 0.35, 300)

    # AtlasPOS: 1 active venue now, conservative 1 new venue/month
    pos_base = 79
    pos_growth_per_mo = 79  # 1 new venue/month

    results = []
    for _ in range(n):
        total = 0
        for mo in range(6):  # Jul through Dec
            consulting = max(0, random.gauss(base_mean, base_std))
            new_venues  = max(0, int(random.gauss(1, 0.5)))
            pos_mrr     = pos_base + pos_growth_per_mo * mo + new_venues * 79
            total += consulting + pos_mrr
        results.append(total)

    results.sort()
    full_yr_actuals = sum(actuals_2026.values())
    targets = {
        'beat_2025': sum(ACTUALS_2025.values()),
        'break_even_monthly': 3000,
    }

    return {
        'p10': round(results[int(n * .10)]),
        'p50': round(results[int(n * .50)]),
        'p90': round(results[int(n * .90)]),
        'full_year_p50': round(full_yr_actuals + results[int(n * .50)]),
        'prob_beat_full_2025_pct': round(
            sum(1 for r in results if full_yr_actuals + r > targets['beat_2025']) / n * 100
        ),
        'months': ['Jul','Aug','Sep','Oct','Nov','Dec'],
    }

def ltv(arpu=79, margin=0.90, churn=0.04):
    return {
        'arpu': arpu, 'margin_pct': int(margin*100),
        'churn_assumption_pct': int(churn*100),
        'ltv': round(arpu * margin / churn),
        'lifetime_months': round(1/churn),
        'cac_payback_3x_rule': round(arpu * margin / churn / 3),
    }

def main():
    a2025, a2026 = fetch_odoo()
    if not a2025: a2025 = ACTUALS_2025
    if not a2026: a2026 = ACTUALS_2026_FALLBACK
    source = 'odoo' if (a2026 is not ACTUALS_2026_FALLBACK or a2025 is not ACTUALS_2025) else 'hardcoded'

    ytd26  = sum(a2026.values())
    same25 = sum(a2025.get(k.replace('2026','2025'), 0) for k in a2026)
    yoy    = round((ytd26 - same25) / max(abs(same25), 1) * 100)
    fy25   = sum(a2025.values())

    out = {
        'generated': datetime.now(timezone.utc).isoformat(),
        'data_source': source,
        'revenue': {
            'actuals_2025': a2025,
            'actuals_2026': a2026,
            'ytd_2026': ytd26,
            'ytd_2025_same_period': same25,
            'full_year_2025': fy25,
            'remaining_to_beat_2025': max(0, fy25 - ytd26),
            'yoy_pct': yoy,
        },
        'mrr': {
            'arpu': 79,
            'active_venues': 1,
            'current_mrr': 79,
            'target_venues': 52,
            'mrr_at_target': 52 * 79,
        },
        'forecast_h2': monte_carlo(a2026),
        'ltv': ltv(),
        'market': {
            'verified_facts': [
                {'label':'NL GDP growth','value':'+1.4%','source':'CPB 2025'},
                {'label':'Horeca turnover growth','value':'+3.4%','source':'CBS 2024'},
                {'label':'Real consumer spending','value':'+1–1.5%','source':'ING 2025'},
                {'label':'Contactless payments','value':'94%','source':'DNB end-2024'},
                {'label':'Pin transactions 2024','value':'5.77B (+2.9%)','source':'Betaalvereniging NL'},
            ],
            'tam_tilburg_horeca': '~600 venues',
            'ccv_entry_price': '€25–€34.50/mo',
            'expansion_verdict': 'Sign 1 AtlasPOS venue/week → 52 venues by Jun 2027 → €4,108/mo MRR',
        },
    }

    os.makedirs(os.path.dirname(OUT_PATH), exist_ok=True)
    with open(OUT_PATH, 'w') as f:
        json.dump(out, f, indent=2)
    print(f'[intelligence] Written → {OUT_PATH}')
    print(f'[intelligence] YTD 2026: €{ytd26:,} | YoY: {yoy:+}% | source: {source}')

if __name__ == '__main__':
    main()
