/* global React, sbx, audit, auditSnapshot, chunk */
// Admin data layer for a player's match history: list, view/edit one match
// (header fields + hole-by-hole), and delete. Requires v1/sql/admin-match-crud.sql
// (admin UPDATE/DELETE on matches + match_holes) on top of the SELECT policies
// manager-read-matches.sql already grants admins.

// Every match this user appears in, across all four possible seats.
function useUserMatches(userId) {
  const [rows, setRows] = React.useState(null);
  const [error, setError] = React.useState('');
  const load = React.useCallback(async () => {
    if (!userId) { setRows([]); return; }
    const { data, error } = await sbx
      .from('matches')
      .select(`
        id, join_code, course_name, match_type, status, result, final_margin, total_holes,
        created_at, started_at, completed_at,
        player_a, player_a2, player_b, player_b2,
        pa:profiles!matches_player_a_fkey(id, first_name, last_name, handle),
        pb:profiles!matches_player_b_fkey(id, first_name, last_name, handle)
      `)
      .or(`player_a.eq.${userId},player_a2.eq.${userId},player_b.eq.${userId},player_b2.eq.${userId}`)
      .order('created_at', { ascending: false });
    if (error) { setError(error.message || 'Could not load matches.'); setRows([]); return; }
    setError('');
    setRows(data || []);
  }, [userId]);
  React.useEffect(() => { load(); }, [load]);
  return [rows, error, load];
}

// One match, fully expanded: header + every player's name (player_a2/b2 need
// a separate lookup — the FK join above only covers seat 1 of each team) +
// its hole-by-hole rows.
function useMatchDetail(matchId) {
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState('');
  const load = React.useCallback(async () => {
    if (!matchId) { setData(null); return; }
    const { data: m, error: mErr } = await sbx.from('matches').select('*').eq('id', matchId).single();
    if (mErr) { setError(mErr.message || 'Could not load the match.'); setData(null); return; }
    const seatIds = [m.player_a, m.player_a2, m.player_b, m.player_b2].filter(Boolean);
    const { data: profiles } = seatIds.length
      ? await sbx.from('profiles').select('id, first_name, last_name, handle').in('id', seatIds)
      : { data: [] };
    const byId = {}; (profiles || []).forEach(p => { byId[p.id] = p; });
    const { data: holes } = await sbx.from('match_holes').select('*').eq('match_id', matchId).order('hole_number');
    setError('');
    setData({ match: m, players: byId, holes: holes || [] });
  }, [matchId]);
  React.useEffect(() => { load(); }, [load]);
  return [data, error, load];
}

async function updateMatch(matchId, patch) {
  const { data: was } = await sbx.from('matches')
    .select('course_name, status, result, final_margin, total_holes').eq('id', matchId).maybeSingle();
  const { error } = await sbx.from('matches').update(patch).eq('id', matchId);
  if (error) throw humanizeMatch(error);
  // An admin editing a result overrides what the players recorded, and SBX
  // ratings are computed from these. Worth knowing it was changed by hand.
  await audit('match_edit', 'matches', matchId,
    auditSnapshot(was, ['course_name', 'status', 'result', 'final_margin', 'total_holes']), patch);
}

// Upsert one hole row — the editor always has a full 1..total_holes grid, so
// a hole with no existing row yet (never scored) inserts on first edit.
async function upsertMatchHole(matchId, holeNumber, patch) {
  const { error } = await sbx.from('match_holes')
    .upsert({ match_id: matchId, hole_number: holeNumber, ...patch }, { onConflict: 'match_id,hole_number' });
  if (error) throw humanizeMatch(error);
}

async function deleteMatch(matchId) {
  const { data: was } = await sbx.from('matches')
    .select('course_name, status, result, final_margin, player_a, player_b, created_at')
    .eq('id', matchId).maybeSingle();
  const { error } = await sbx.from('matches').delete().eq('id', matchId);
  if (error) throw humanizeMatch(error);
  await audit('match_delete', 'matches', matchId,
    auditSnapshot(was, ['course_name', 'status', 'result', 'final_margin', 'player_a', 'player_b', 'created_at']), null);
}

// Delete several matches in one action — cleaning up test data is the whole
// reason this exists, and test data rarely comes as a single row.
//
// One audit row for the whole batch, not one per match: the individual path
// above already writes a full snapshot per match for the rare single delete,
// but a 40-row cleanup would otherwise flood the Activity feed with 40
// identical-looking lines and bury the one delete somebody actually needs to
// find later. The batch row keeps enough to reconstruct what was removed —
// every match's course, opponent pairing and result — without keeping a full
// row-for-row copy.
async function deleteMatches(matchIds) {
  const ids = [...new Set(matchIds)].filter(Boolean);
  if (!ids.length) return { deleted: 0 };

  let was = [];
  for (const group of chunk(ids, 200)) {
    // eslint-disable-next-line no-await-in-loop
    const { data } = await sbx.from('matches')
      .select('id, course_name, status, result, player_a, player_b')
      .in('id', group);
    was = was.concat(data || []);
  }

  for (const group of chunk(ids, 200)) {
    // eslint-disable-next-line no-await-in-loop
    const { error } = await sbx.from('matches').delete().in('id', group);
    if (error) throw humanizeMatch(error);
  }

  await audit('match_bulk_delete', 'matches', null, {
    count: was.length,
    matches: was.map(m => ({ id: m.id, course_name: m.course_name, status: m.status, result: m.result })),
  }, null);

  return { deleted: was.length };
}

function humanizeMatch(error) {
  const msg = (error && error.message) || 'Something went wrong.';
  if (/row-level security|permission/i.test(msg)) return new Error('Not allowed — your account needs admin access, and v1/sql/admin-match-crud.sql needs to be applied.');
  return new Error(msg);
}

Object.assign(window, { useUserMatches, useMatchDetail, updateMatch, upsertMatchHole, deleteMatch, deleteMatches });
