const IDENTIFIER = /^[a-z][a-z0-9._:-]{2,127}$/;

export async function resolveManagedReaderSourceConnection(db, policy) {
  const tenantId = identifier(policy?.tenantId);
  const consumerConnectionId = identifier(policy?.connectionId);
  const providerId = identifier(policy?.providerId);
  const allowedDataProductIds = uniqueIdentifiers(policy?.allowedDataProductIds);
  if (!tenantId || !consumerConnectionId || !providerId || !allowedDataProductIds.length) {
    throw sourceScopeError("managed_reader_source_scope_request_invalid", 400);
  }

  const result = await db.query(
    `select distinct candidate.connection_id as "connectionId"
     from external_data_plane_writer_bindings as candidate
     where candidate.tenant_id = $1
       and candidate.provider_id = $2
       and candidate.active = true
       and (candidate.expires_at is null or candidate.expires_at > now())
       and not exists (
         select 1
         from unnest($3::text[]) as requested(data_product_id)
         where not exists (
           select 1
           from external_data_plane_writer_bindings as coverage
           where coverage.tenant_id = candidate.tenant_id
             and coverage.connection_id = candidate.connection_id
             and coverage.provider_id = candidate.provider_id
             and coverage.active = true
             and (coverage.expires_at is null or coverage.expires_at > now())
             and coverage.allowed_data_product_ids ? requested.data_product_id
         )
       )
     order by candidate.connection_id asc
     limit 3`,
    [tenantId, providerId, allowedDataProductIds],
  );
  const candidates = uniqueIdentifiers(result.rows.map((row) => row.connectionId));
  if (candidates.includes(consumerConnectionId)) return consumerConnectionId;
  if (candidates.length === 1) return candidates[0];
  if (!candidates.length) throw sourceScopeError("managed_reader_source_scope_not_found", 409);
  throw sourceScopeError("managed_reader_source_scope_ambiguous", 409);
}

export async function resolveManagedReaderScope(db, allowedDataProductIdsValue) {
  const allowedDataProductIds = uniqueIdentifiers(allowedDataProductIdsValue);
  if (!allowedDataProductIds.length) {
    throw sourceScopeError("managed_consumer_reader_scope_request_invalid", 400);
  }
  const result = await db.query(
    `select distinct candidate.tenant_id as "tenantId",
       candidate.connection_id as "connectionId", candidate.provider_id as "providerId"
     from external_data_plane_writer_bindings as candidate
     where candidate.active = true
       and (candidate.expires_at is null or candidate.expires_at > now())
       and not exists (
         select 1
         from unnest($1::text[]) as requested(data_product_id)
         where not exists (
           select 1
           from external_data_plane_writer_bindings as coverage
           where coverage.tenant_id = candidate.tenant_id
             and coverage.connection_id = candidate.connection_id
             and coverage.provider_id = candidate.provider_id
             and coverage.active = true
             and (coverage.expires_at is null or coverage.expires_at > now())
             and coverage.allowed_data_product_ids ? requested.data_product_id
         )
       )
     order by candidate.tenant_id asc, candidate.provider_id asc, candidate.connection_id asc
     limit 3`,
    [allowedDataProductIds],
  );
  const candidates = result.rows
    .map((row) => ({
      tenantId: identifier(row.tenantId),
      connectionId: identifier(row.connectionId),
      providerId: identifier(row.providerId),
    }))
    .filter((row) => row.tenantId && row.connectionId && row.providerId);
  if (candidates.length === 1) return Object.freeze(candidates[0]);
  if (!candidates.length) throw sourceScopeError("managed_consumer_reader_source_scope_not_found", 409);
  throw sourceScopeError("managed_consumer_reader_source_scope_ambiguous", 409);
}

export async function backfillManagedReaderSourceConnections(db) {
  const unresolved = await db.query(
    `select id, tenant_id as "tenantId", connection_id as "connectionId",
       provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds"
     from external_data_plane_reader_bindings
     where binding_key is not null and source_connection_id is null
     order by created_at asc, id asc`,
  );
  let resolved = 0;
  let unresolvedCount = 0;
  for (const binding of unresolved.rows) {
    try {
      const sourceConnectionId = await resolveManagedReaderSourceConnection(db, binding);
      const result = await db.query(
        `update external_data_plane_reader_bindings
         set source_connection_id = $2
         where id = $1 and binding_key is not null and source_connection_id is null`,
        [binding.id, sourceConnectionId],
      );
      resolved += result.rowCount;
    } catch (error) {
      if (!new Set([
        "managed_reader_source_scope_not_found",
        "managed_reader_source_scope_ambiguous",
      ]).has(error?.code)) throw error;
      unresolvedCount += 1;
    }
  }
  return { resolved, unresolved: unresolvedCount };
}

function sourceScopeError(code, status) {
  return Object.assign(new Error(code), { code, status });
}

function identifier(value) {
  const normalized = typeof value === "string" ? value.trim() : "";
  return IDENTIFIER.test(normalized) ? normalized : "";
}

function uniqueIdentifiers(value) {
  if (!Array.isArray(value)) return [];
  return [...new Set(value.map(identifier).filter(Boolean))];
}
