import { getDb } from '$lib/server/db';

// ---------------------------------------------------------------------------
// Admin check (reads ADMIN_USERS env var, same as AdminService in main src)
// ---------------------------------------------------------------------------

function normalizeId(raw: string | null | undefined): string {
  if (!raw) return '';
  return String(raw).replace(/[@:+\-\s]/g, '').trim();
}

function loadAdmins(): Set<string> {
  const raw = String(process.env.ADMIN_USERS || '');
  const set = new Set<string>();
  for (const token of raw.split(',').map(s => s.trim()).filter(Boolean)) {
    const n = normalizeId(token);
    if (n) set.add(n);
  }
  return set;
}

function isAdmin(userId: string | null | undefined): boolean {
  const n = normalizeId(userId);
  if (!n) return false;
  return loadAdmins().has(n);
}

// ---------------------------------------------------------------------------

/**
 * Validate session and parse JSON body for POST endpoints.
 * Returns { userId, payload } on success, or a Response on failure.
 * Callers should check `instanceof Response` before destructuring.
 */
export async function requireAuthAndJson(event: {
  locals: { userId?: string | null };
  request: { json(): Promise<any> };
}): Promise<{ userId: string; payload: any } | Response> {
  const userId = event.locals.userId ?? null;
  if (!userId) return new Response('Unauthorized', { status: 401 });

  let payload: any = null;
  try {
    payload = await event.request.json();
  } catch {
    return new Response('Bad Request', { status: 400 });
  }

  return { userId, payload };
}

/**
 * Shared auth + task loading logic used by task detail, claim, and unassign routes.
 *
 * Validates the user, parses the task ID from params, opens the DB, loads the task,
 * and checks that it exists and is not completed.  Returns the context on success
 * or a Response on failure — callers should check `instanceof Response` first.
 */
export async function loadAndCheckTask(event: {
  locals: { userId?: string | null };
  params: { id?: string };
}): Promise<{ db: any; task: any; userId: string } | Response> {
  const ctx = await _loadTask(event);
  if (ctx instanceof Response) return ctx;

  // Additional check: reject completed tasks
  const { task } = ctx;
  if (Number(task.completed) !== 0 || task.completed_at) {
    return new Response(JSON.stringify({ status: 'completed' }), {
      status: 400,
      headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
    });
  }

  return ctx;
}

/**
 * Shared group gating check: verifies the group is allowed and the user
 * is an active member.  Returns a 403 Response on failure, or true to
 * continue.  Callers should `if (res instanceof Response) return res;`.
 */
export function checkGroupAccess(
  db: any,
  groupId: string | null,
  userId: string
): Response | true {
  if (!groupId) return true;

  const allowed = db
    .prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? AND status = 'allowed' LIMIT 1`)
    .get(groupId);
  const active = db
    .prepare(
      `SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ? AND is_active = 1 LIMIT 1`
    )
    .get(groupId, userId);

  if (!allowed || !active) {
    return new Response('Forbidden', { status: 403 });
  }
  return true;
}

/**
 * Auth + task load + full gating (group + personal assignment).
 * Returns context or a Response on failure.  Does NOT check completed status —
 * callers must handle that themselves (complete vs uncomplete have opposite
 * semantics).
 */
/**
 * Load a task, check auth, and verify group access.
 * Returns { db, task, userId } or a Response on failure.
 * Does NOT check personal assignment (suitable for claim/unassign routes).
 */
export async function loadTaskAndCheckGroup(event: {
  locals: { userId?: string | null };
  params: { id?: string };
}): Promise<{ db: any; task: any; userId: string } | Response> {
  const ctx = await loadAndCheckTask(event);
  if (ctx instanceof Response) return ctx;
  const { db, task, userId } = ctx;

  // Gating: grupo permitido + usuario miembro activo
  const groupId: string | null = task.group_id ? String(task.group_id) : null;
  const gating = checkGroupAccess(db, groupId, userId);
  if (gating instanceof Response) return gating;

  return { db, task, userId };
}

/**
 * Fetch allowed groups for a user where the user is an active member.
 *
 * @param excludeCommunityArchived - when true, also filters out
 *   community groups (is_community=0) and archived groups (archived=0).
 *   Defaults to false (includes all active allowed groups).
 */
export function fetchAllowedUserGroups(
  db: any,
  userId: string,
  opts?: { excludeCommunityArchived?: boolean }
): Array<{ id: string; name: string | null }> {
  const extraWhere = opts?.excludeCommunityArchived
    ? ' AND COALESCE(g.is_community, 0) = 0 AND COALESCE(g.archived, 0) = 0'
    : '';

  return db
    .prepare(
      `SELECT g.id, g.name
       FROM groups g
       INNER JOIN group_members gm
         ON gm.group_id = g.id AND gm.user_id = ? AND gm.is_active = 1
       INNER JOIN allowed_groups ag
         ON ag.group_id = g.id AND ag.status = 'allowed'
       WHERE COALESCE(g.active, 1) = 1${extraWhere}
       ORDER BY (g.name IS NULL) ASC, g.name ASC, g.id ASC`
    )
    .all(userId) as Array<{ id: string; name: string | null }>;
}

/**
 * Low-level: auth + taskId parsing + DB + task load + not-found check.
 * Does NOT reject completed tasks — that's up to the caller.
 */
async function _loadTask(event: {
  locals: { userId?: string | null };
  params: { id?: string };
}): Promise<{ db: any; task: any; userId: string } | Response> {
  // Auth
  const userId = event.locals.userId ?? null;
  if (!userId) return new Response('Unauthorized', { status: 401 });

  // Parse task ID
  const idStr = event.params.id || '';
  const taskId = parseInt(idStr, 10);
  if (!Number.isFinite(taskId) || taskId <= 0) return new Response('Bad Request', { status: 400 });

  // DB
  const db = await getDb();

  // Load
  const task = db
    .prepare(
      `SELECT id, description, due_date, group_id, created_by,
              COALESCE(completed, 0) AS completed, completed_at, display_code
       FROM tasks
       WHERE id = ?`
    )
    .get(taskId) as any;

  if (!task) {
    return new Response(JSON.stringify({ status: 'not_found' }), {
      status: 404,
      headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
    });
  }

  return { db, task, userId };
}

export async function loadTaskAndGating(event: {
  locals: { userId?: string | null };
  params: { id?: string };
}): Promise<{ db: any; task: any; userId: string; groupId: string | null } | Response> {
  const ctx = await _loadTask(event);
  if (ctx instanceof Response) return ctx;
  const { db, task, userId } = ctx;

  // Admin override: admins can complete any task
  if (isAdmin(userId)) {
    const groupId: string | null = task.group_id ? String(task.group_id) : null;
    return { db, task, userId, groupId };
  }

  // Gating: grupo allowed + miembro activo; si no tiene grupo, debe estar asignado
  const groupId: string | null = task.group_id ? String(task.group_id) : null;
  const gating = checkGroupAccess(db, groupId, userId);
  if (gating instanceof Response) return gating;
  if (!groupId) {
    const isAssigned = db
      .prepare(`SELECT 1 FROM task_assignments WHERE task_id = ? AND user_id = ? LIMIT 1`)
      .get(task.id, userId);
    if (!isAssigned) {
      return new Response('Forbidden', { status: 403 });
    }
  }

  return { db, task, userId, groupId };
}

/** Convert a DB row to the standard API task shape. */
export function formatTask(row: any): Record<string, any> {
  return {
    id: Number(row.id),
    description: String(row.description || ''),
    due_date: row.due_date ? String(row.due_date) : null,
    display_code: row.display_code != null ? Number(row.display_code) : null,
    completed: 'completed' in (row || {}) ? Number(row.completed || 0) : undefined,
    completed_at: 'completed_at' in (row || {}) ? (row.completed_at ? String(row.completed_at) : null) : undefined
  };
}

/** Map a DB row to a task list item (id, desc, date, group, code, assignees). */
export function mapTaskRow(r: any): Record<string, any> {
  return {
    id: Number(r.id),
    description: String(r.description || ''),
    due_date: r.due_date ? String(r.due_date) : null,
    group_id: r.group_id ? String(r.group_id) : null,
    display_code: r.display_code != null ? Number(r.display_code) : null,
    assignees: [] as string[]
  };
}

/**
 * Populate item.assignees by batch-loading task_assignments.
 * Optionally computes can_unassign for the given userId (pass null to skip).
 */
export function loadAssignees(db: any, items: any[], userId: string | null): void {
	if (items.length === 0) return;
	const ids = items.map((it) => it.id);
	const placeholders = ids.map(() => '?').join(',');
	const assignRows = db
		.prepare(
			`SELECT task_id, user_id
			 FROM task_assignments
			 WHERE task_id IN (${placeholders})
			 ORDER BY assigned_at ASC`
		)
		.all(...ids) as any[];

	const map = new Map<number, string[]>();
	for (const row of assignRows) {
		const tid = Number(row.task_id);
		const uid = String(row.user_id);
		if (!map.has(tid)) map.set(tid, []);
		map.get(tid)!.push(uid);
	}
	for (const it of items) {
		it.assignees = map.get(it.id) || [];
		if (userId != null) {
			const personal = it.group_id == null;
			const cnt = Array.isArray(it.assignees) ? it.assignees.length : 0;
			const mine = (it.assignees || []).some((uid: string) => uid === userId);
			(it as any).can_unassign = !(personal && cnt === 1 && mine);
		}
	}
}

/** Build a 200 JSON response { status, task }. */
export function respondTask(status: string, task: Record<string, any>): Response {
  return new Response(JSON.stringify({ status, task }), {
    status: 200,
    headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
  });
}
