const pool = require("../db/pool");

function getExecutor(client) {
  return client || pool;
}

async function withTransaction(handler) {
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    const result = await handler(client);
    await client.query("COMMIT");
    return result;
  } catch (error) {
    await client.query("ROLLBACK");
    throw error;
  } finally {
    client.release();
  }
}

async function getManageableGroupsForUser(userId, client) {
  const result = await getExecutor(client).query(
    `SELECT household_id AS group_id
     FROM household_members
     WHERE user_id = $1
       AND role IN ('owner', 'admin')`,
    [userId]
  );
  return result.rows;
}

async function getUserGroupRole(groupId, userId, client) {
  const result = await getExecutor(client).query(
    `SELECT role
     FROM household_members
     WHERE household_id = $1
       AND user_id = $2`,
    [groupId, userId]
  );
  return result.rows[0]?.role || null;
}

async function getGroupById(groupId, client) {
  const result = await getExecutor(client).query(
    `SELECT id, name
     FROM households
     WHERE id = $1`,
    [groupId]
  );
  return result.rows[0] || null;
}

async function listInviteLinks(groupId, client) {
  const result = await getExecutor(client).query(
    `SELECT
      id,
      group_id,
      created_by,
      token,
      policy,
      single_use,
      expires_at,
      used_at,
      revoked_at,
      created_at
    FROM group_invite_links
    WHERE group_id = $1
    ORDER BY created_at DESC`,
    [groupId]
  );
  return result.rows;
}

async function createInviteLink(
  { groupId, createdBy, token, policy, singleUse, expiresAt },
  client
) {
  const result = await getExecutor(client).query(
    `INSERT INTO group_invite_links (
      group_id,
      created_by,
      token,
      policy,
      single_use,
      expires_at
    ) VALUES ($1, $2, $3, $4, $5, $6)
    RETURNING
      id,
      group_id,
      created_by,
      token,
      policy,
      single_use,
      expires_at,
      used_at,
      revoked_at,
      created_at`,
    [groupId, createdBy, token, policy, singleUse, expiresAt]
  );
  return result.rows[0];
}

async function getInviteLinkById(groupId, linkId, client) {
  const result = await getExecutor(client).query(
    `SELECT
      id,
      group_id,
      created_by,
      token,
      policy,
      single_use,
      expires_at,
      used_at,
      revoked_at,
      created_at
    FROM group_invite_links
    WHERE group_id = $1
      AND id = $2`,
    [groupId, linkId]
  );
  return result.rows[0] || null;
}

async function revokeInviteLink(groupId, linkId, client) {
  const result = await getExecutor(client).query(
    `UPDATE group_invite_links
     SET revoked_at = NOW()
     WHERE group_id = $1
       AND id = $2
     RETURNING
       id,
       group_id,
       created_by,
       token,
       policy,
       single_use,
       expires_at,
       used_at,
       revoked_at,
       created_at`,
    [groupId, linkId]
  );
  return result.rows[0] || null;
}

async function reviveInviteLink(groupId, linkId, expiresAt, client) {
  const result = await getExecutor(client).query(
    `UPDATE group_invite_links
     SET used_at = NULL,
         revoked_at = NULL,
         expires_at = $3
     WHERE group_id = $1
       AND id = $2
     RETURNING
       id,
       group_id,
       created_by,
       token,
       policy,
       single_use,
       expires_at,
       used_at,
       revoked_at,
       created_at`,
    [groupId, linkId, expiresAt]
  );
  return result.rows[0] || null;
}

async function deleteInviteLink(groupId, linkId, client) {
  const result = await getExecutor(client).query(
    `DELETE FROM group_invite_links
     WHERE group_id = $1
       AND id = $2
     RETURNING
       id,
       group_id,
       created_by,
       token,
       policy,
       single_use,
       expires_at,
       used_at,
       revoked_at,
       created_at`,
    [groupId, linkId]
  );
  return result.rows[0] || null;
}

async function getInviteLinkSummaryByToken(token, client, forUpdate = false) {
  const result = await getExecutor(client).query(
    `SELECT
      gil.id,
      gil.group_id,
      gil.created_by,
      gil.token,
      gil.policy,
      gil.single_use,
      gil.expires_at,
      gil.used_at,
      gil.revoked_at,
      gil.created_at,
      h.name AS group_name,
      gs.join_policy AS current_join_policy
    FROM group_invite_links gil
    JOIN households h ON h.id = gil.group_id
    LEFT JOIN group_settings gs ON gs.group_id = gil.group_id
    WHERE gil.token = $1
    ${forUpdate ? "FOR UPDATE OF gil" : ""}`,
    [token]
  );
  return result.rows[0] || null;
}

async function isGroupMember(groupId, userId, client) {
  const result = await getExecutor(client).query(
    `SELECT 1
     FROM household_members
     WHERE household_id = $1
       AND user_id = $2`,
    [groupId, userId]
  );
  return result.rows.length > 0;
}

async function getPendingJoinRequest(groupId, userId, client) {
  const result = await getExecutor(client).query(
    `SELECT id, group_id, user_id, status, created_at, updated_at
     FROM group_join_requests
     WHERE group_id = $1
       AND user_id = $2
       AND status = 'PENDING'`,
    [groupId, userId]
  );
  return result.rows[0] || null;
}

async function createOrTouchPendingJoinRequest(groupId, userId, client) {
  const executor = getExecutor(client);
  const existing = await executor.query(
    `UPDATE group_join_requests
     SET updated_at = NOW()
     WHERE group_id = $1
       AND user_id = $2
       AND status = 'PENDING'
     RETURNING id, group_id, user_id, status, created_at, updated_at`,
    [groupId, userId]
  );
  if (existing.rows[0]) {
    return existing.rows[0];
  }

  try {
    const inserted = await executor.query(
      `INSERT INTO group_join_requests (group_id, user_id, status)
       VALUES ($1, $2, 'PENDING')
       RETURNING id, group_id, user_id, status, created_at, updated_at`,
      [groupId, userId]
    );
    return inserted.rows[0];
  } catch (error) {
    if (error.code !== "23505") {
      throw error;
    }
    const fallback = await executor.query(
      `SELECT id, group_id, user_id, status, created_at, updated_at
       FROM group_join_requests
       WHERE group_id = $1
         AND user_id = $2
         AND status = 'PENDING'
       LIMIT 1`,
      [groupId, userId]
    );
    return fallback.rows[0] || null;
  }
}

async function addGroupMember(groupId, userId, role = "member", client) {
  const result = await getExecutor(client).query(
    `INSERT INTO household_members (household_id, user_id, role)
     VALUES ($1, $2, $3)
     ON CONFLICT (household_id, user_id) DO NOTHING
     RETURNING id`,
    [groupId, userId, role]
  );
  return result.rows.length > 0;
}

async function consumeSingleUseInvite(linkId, client) {
  const result = await getExecutor(client).query(
    `UPDATE group_invite_links
     SET used_at = NOW(),
         revoked_at = NOW()
     WHERE id = $1
     RETURNING id`,
    [linkId]
  );
  return result.rows.length > 0;
}

async function getGroupSettings(groupId, client) {
  const result = await getExecutor(client).query(
    `SELECT group_id, join_policy
     FROM group_settings
     WHERE group_id = $1`,
    [groupId]
  );
  return result.rows[0] || null;
}

async function upsertGroupSettings(groupId, joinPolicy, client) {
  const result = await getExecutor(client).query(
    `INSERT INTO group_settings (group_id, join_policy)
     VALUES ($1, $2)
     ON CONFLICT (group_id)
     DO UPDATE SET
       join_policy = EXCLUDED.join_policy,
       updated_at = NOW()
     RETURNING group_id, join_policy`,
    [groupId, joinPolicy]
  );
  return result.rows[0];
}

async function createGroupAuditLog(
  {
    groupId,
    actorUserId,
    actorRole,
    eventType,
    requestId,
    ip,
    userAgent,
    success = true,
    errorCode = null,
    metadata = {},
  },
  client
) {
  const result = await getExecutor(client).query(
    `INSERT INTO group_audit_log (
      group_id,
      actor_user_id,
      actor_role,
      event_type,
      request_id,
      ip,
      user_agent,
      success,
      error_code,
      metadata
    ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb)
    RETURNING id`,
    [
      groupId,
      actorUserId,
      actorRole,
      eventType,
      requestId,
      ip,
      userAgent,
      success,
      errorCode,
      JSON.stringify(metadata || {}),
    ]
  );
  return result.rows[0];
}

module.exports = {
  addGroupMember,
  createGroupAuditLog,
  createInviteLink,
  createOrTouchPendingJoinRequest,
  consumeSingleUseInvite,
  deleteInviteLink,
  getGroupById,
  getGroupSettings,
  getInviteLinkById,
  getInviteLinkSummaryByToken,
  getManageableGroupsForUser,
  getPendingJoinRequest,
  getUserGroupRole,
  isGroupMember,
  listInviteLinks,
  revokeInviteLink,
  reviveInviteLink,
  upsertGroupSettings,
  withTransaction,
};
