'use strict';

/**
 * /api/profiles — public ledger profiles v1 (A1 Session 10).
 *
 * Stage-3 seed: a user claims a handle and publishes their ENTIRE settled
 * ledger record — wins and misses, nothing curated. Strava for betting.
 *
 *   GET  /me       (auth)   — own public_profiles row (or null).
 *   POST /me       (auth)   — upsert own { handle, published }. Handle must
 *                             match ^[a-z0-9_]{3,20}$; a handle owned by
 *                             another user → 409. Service-role write only
 *                             (the table has no client write policies).
 *   GET  /:handle  (public) — the published record: the user-scoped 30d
 *                             aggregate (same n≥20 gate as the model record)
 *                             + settled rows, newest 50, same columns as
 *                             /api/ledger.
 *
 * PRIVACY: PRIVATE BY DEFAULT — `published` only flips via the explicit
 * toggle. NO EXISTENCE LEAK: an unknown handle and an unpublished handle
 * return the byte-identical 404 body.
 *
 * HOUSE PROFILE (Wave 5A, D2) — the reserved `HOUSE_HANDLE` (default `vyndr`)
 * resolves to the PUBLIC model record (`getModelAggregate()` with NO userId →
 * the `user_id = NULL` ledger rows). It needs NO public_profiles row and is
 * ALWAYS public (it's the partner-pitch weapon: the real house record). It is
 * the ONLY special case; every OTHER handle keeps the private-by-default,
 * no-existence-leak contract intact.
 */

const express = require('express');
const { createRateLimit } = require('../middleware/rateLimit');
const { requireAuth } = require('../middleware/auth');
const ledgerService = require('../services/ledgerService');

const router = express.Router();
router.use(createRateLimit({ windowMs: 60_000, max: 60 }));

const HANDLE_RE = /^[a-z0-9_]{3,20}$/;
// Reserved house handle → the public model record (user_id = NULL). Operators
// can override via env; it must still satisfy HANDLE_RE to be reachable.
const HOUSE_HANDLE = String(process.env.HOUSE_HANDLE || 'vyndr').trim().toLowerCase();
const HOUSE_LABEL = 'VYNDR MODEL · PUBLIC RECORD';
// Same 404 body for unknown AND unpublished — never confirm a handle exists.
const NOT_FOUND = { error: 'Profile not found' };
// Same columns as /api/ledger (routes/ledger.js ROW_COLUMNS).
const ROW_COLUMNS = 'id, player_key, player_name, sport, stat, line, side, locked_odds, book, grade, edge, confidence, model_value, graded_at, game_id, game_date, closing_line, closing_odds, clv, clv_result, outcome, actual_value, settled_at, revised_from_grade';
const ENTRY_LIMIT = 50;

function sbOrNull() {
  try {
    if (!ledgerService.__internals.isConfigured()) return null;
    return require('../utils/supabase').getSupabaseServiceClient();
  } catch { return null; }
}

// Own row — the owner sees it published or not.
router.get('/me', requireAuth, async (req, res) => {
  const sb = sbOrNull();
  if (!sb) return res.json({ profile: null });
  try {
    const { data, error } = await sb.from('public_profiles')
      .select('handle, published, created_at')
      .eq('user_id', req.user.id)
      .maybeSingle();
    if (error) throw new Error(error.message);
    return res.json({ profile: data || null });
  } catch (err) {
    console.error('[profiles/me]', err.message);
    return res.status(200).json({ profile: null });
  }
});

// Claim/update the handle + the ONE explicit publish toggle.
router.post('/me', requireAuth, async (req, res) => {
  const sb = sbOrNull();
  if (!sb) return res.status(503).json({ error: 'Profiles are unavailable right now' });
  try {
    const body = req.body || {};
    const handle = String(body.handle || '').trim().toLowerCase();
    if (!HANDLE_RE.test(handle)) {
      return res.status(400).json({ error: 'Handle must be 3-20 characters: a-z, 0-9, underscore' });
    }
    // PRIVATE BY DEFAULT: published only flips when the body says true.
    const published = body.published === true;
    const { error } = await sb.from('public_profiles')
      .upsert({ user_id: req.user.id, handle, published }, { onConflict: 'user_id' });
    if (error) {
      // Unique violation on `handle` → someone else owns it.
      if (error.code === '23505' || /duplicate|unique/i.test(error.message || '')) {
        return res.status(409).json({ error: 'That handle is taken' });
      }
      throw new Error(error.message);
    }
    return res.json({ ok: true, profile: { handle, published } });
  } catch (err) {
    console.error('[profiles/post]', err.message);
    return res.status(503).json({ error: 'Could not save profile' });
  }
});

// The public record. Cache lightly; the settle pass updates rows a few
// times a day, not per-second.
router.get('/:handle', async (req, res) => {
  const handle = String(req.params.handle || '').trim().toLowerCase();
  // Invalid shape can't exist (DB CHECK) → same 404, no query needed.
  if (!HANDLE_RE.test(handle)) return res.status(404).json(NOT_FOUND);
  // HOUSE handle → the public model record. Checked BEFORE the
  // public_profiles lookup so the handle is reserved (a user who claims it is
  // shadowed). This is the ONLY handle that bypasses the publish gate.
  if (handle === HOUSE_HANDLE) return sendHouseProfile(res);
  const sb = sbOrNull();
  if (!sb) return res.status(404).json(NOT_FOUND);
  try {
    const { data: row, error } = await sb.from('public_profiles')
      .select('user_id, handle, published')
      .eq('handle', handle)
      .maybeSingle();
    if (error) throw new Error(error.message);
    // Unknown and unpublished are indistinguishable from outside.
    if (!row || row.published !== true) return res.status(404).json(NOT_FOUND);

    // Same aggregate machinery as the model record — same window, same
    // n≥20 gate — scoped to this user's own rows.
    const aggregate = await ledgerService.getModelAggregate({ userId: row.user_id, sb });

    // ALL settled reads, misses included — nothing curated.
    const { data: entries, error: entriesErr } = await sb.from('ledger_entries')
      .select(ROW_COLUMNS)
      .eq('user_id', row.user_id)
      .not('outcome', 'is', null)
      .order('graded_at', { ascending: false })
      .limit(ENTRY_LIMIT);
    if (entriesErr) throw new Error(entriesErr.message);

    res.set('Cache-Control', 'public, max-age=60');
    return res.json({
      handle: row.handle,
      aggregate,
      entries: entries || [],
      min_sample: ledgerService.MIN_AGG_SAMPLE,
    });
  } catch (err) {
    console.error('[profiles/handle]', err.message);
    return res.status(404).json(NOT_FOUND);
  }
});

/**
 * The house/model profile — the PUBLIC model record (user_id = NULL), served
 * as a shareable /u profile WITHOUT a public_profiles row. Same response shape
 * the page already consumes (aggregate + by_tier + settled entries), plus
 * `house: true` + a label so the UI can distinguish it from a user profile.
 * It always "exists" → a fetch failure degrades to an honest empty/building
 * state, never a 404.
 */
async function sendHouseProfile(res) {
  const sb = sbOrNull();
  try {
    // NO userId → the public `user_id = NULL` aggregate (the real house
    // record + Wave-3 by_tier). getModelAggregate self-empties without env.
    const aggregate = await ledgerService.getModelAggregate(sb ? { sb } : {});
    let entries = [];
    if (sb) {
      // ALL public settled reads, misses included — nothing curated.
      const { data, error } = await sb.from('ledger_entries')
        .select(ROW_COLUMNS)
        .is('user_id', null)
        .not('outcome', 'is', null)
        .order('graded_at', { ascending: false })
        .limit(ENTRY_LIMIT);
      if (error) throw new Error(error.message);
      entries = data || [];
    }
    res.set('Cache-Control', 'public, max-age=60');
    return res.json({
      handle: HOUSE_HANDLE,
      house: true,
      label: HOUSE_LABEL,
      aggregate,
      entries,
      min_sample: ledgerService.MIN_AGG_SAMPLE,
    });
  } catch (err) {
    console.error('[profiles/house]', err.message);
    return res.status(200).json({
      handle: HOUSE_HANDLE,
      house: true,
      label: HOUSE_LABEL,
      aggregate: null,
      entries: [],
      min_sample: ledgerService.MIN_AGG_SAMPLE,
    });
  }
}

module.exports = router;
