info:
  name: SCRIPT - Get Contracts with two or more contributors
  type: http
  seq: 5

http:
  method: GET
  url: "{{IrisApiUrl}}/contracts;full?year=2026"
  params:
    - name: year
      value: "2026"
      type: query
    - name: wfState
      value: signed
      type: query
      disabled: true
    - name: page
      value: "1"
      type: query
      disabled: true
  auth: inherit

runtime:
  scripts:
    - type: after-response
      code: |-
        // Aggregates contracts with 2+ contributors across all pages and reports summary.
        // // Fixes TypeError: pm.request.url.query.toObject(...).find is not a function by avoiding Array.prototype.find on toObject() result.
        // Adds robust helpers for working with query params across Postman SDK versions and edge cases.
        //
        // Requirements addressed:
        // 1) Read Page-Count header to know total pages
        // 2) Iteratively call same endpoint for pages from current+1 to Page-Count, preserving other query params
        // 3) Aggregate items where contributorSet exists and has length >= 2
        // 4) Build a map pid -> contributorCount across all pages (including initial)
        // 5) Print summary in Test Results and set env var `multiContributorContracts` with [{ pid, contributorCount }]
        // 6) Robust error handling, rate limiting, early stop on non-2xx; handle JSON array or paginated object response shapes
        // 7) Augment aggregation with department (ownerSet[0].organizationUnit.idAb + " - " + description). Keep first non-empty.

        (function () {
          const RATE_LIMIT_DELAY_MS = 200; // small delay between page fetches
          const TARGET_ENV_VAR = 'multiContributorContracts';
          const PAGE_PARAM = 'page';

          // ---------------- URL and Query helpers (SDK-safe) -----------------
          // Returns a plain object of query params. Works with:
          // - pm.request.url.query (SDK v8+ as QueryList) using .toObject()
          // - URL that has no query or is a raw string
          function getQueryObject(url) {
            try {
        //       // pm.request.url can be a Url object or a string. Normalize to raw string.
              const raw = (typeof url === 'string') ? url : (url && (url.toString ? url.toString() : url.raw || ''));
              // Try SDK path first if it's a Url object with .query
              if (url && url.query && typeof url.query.toObject === 'function') {
                const obj = url.query.toObject();
                // toObject may return undefined/null on empty query
                return obj && typeof obj === 'object' ? { ...obj } : {};
              }
              // Fallback: parse the raw string
              if (typeof raw === 'string') {
                const qIndex = raw.indexOf('?');
                if (qIndex === -1) return {};
                const queryStr = raw.substring(qIndex + 1);
                if (!queryStr) return {};
                return queryStr.split('&').reduce((acc, pair) => {
                  if (!pair) return acc;
                  const [k, v] = pair.split('=');
                  if (!k) return acc;
                  acc[decodeURIComponent(k)] = v !== undefined ? decodeURIComponent(v) : '';
                  return acc;
                }, {});
              }
            } catch (e) {
              // fallthrough
            }
            return {};
          }

          function setQueryObject(url, updates) {
            // Returns a new raw URL string with given query params merged
            const raw = (typeof url === 'string') ? url : (url && (url.toString ? url.toString() : url.raw || ''));
            const qIndex = raw.indexOf('?');
            const base = qIndex === -1 ? raw : raw.substring(0, qIndex);
            const current = getQueryObject(url);
            const merged = { ...current, ...updates };
            // Filter out empty/undefined to avoid adding stray keys
            const parts = Object.keys(merged)
              .filter(k => merged[k] !== undefined && merged[k] !== null && merged[k] !== '')
              .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(String(merged[k])));
            return parts.length ? base + '?' + parts.join('&') : base;
          }

          // ---------------- Response shape helpers -----------------
          function isObject(x) { return x && typeof x === 'object' && !Array.isArray(x); }

          function getItemsFromResponseBody(rb) {
            // Supports either an array payload or an object with an array at known keys
            if (Array.isArray(rb)) return rb;
            if (isObject(rb)) {
              // Try common keys: 'items', 'data', 'results'
              if (Array.isArray(rb.items)) return rb.items;
              if (Array.isArray(rb.data)) return rb.data;
              if (Array.isArray(rb.results)) return rb.results;
            }
            return [];
          }

          function safeJson(body) {
            try { return JSON.parse(body); } catch (e) { return null; }
          }

          // ---------------- Aggregation store -----------------
          // Map: pid -> { contributorCount, department }
          const aggregate = {};

          function extractDepartment(item) {
            try {
              const owner0 = Array.isArray(item.ownerSet) && item.ownerSet.length > 0 ? item.ownerSet[0] : null;
              const ou = owner0 && owner0.organizationUnit ? owner0.organizationUnit : null;
              const idAb = ou && typeof ou.idAb === 'string' ? ou.idAb : null;
              const desc = ou && typeof ou.description === 'string' ? ou.description : null;
              if (idAb && desc) return idAb + ' - ' + desc;
              return '';
            } catch (e) {
              return '';
            }
          }

          function considerItems(items) {
            items.forEach(it => {
              const contributors = Array.isArray(it.contributorSet) ? it.contributorSet : [];
              if (contributors.length >= 2) {
                const pid = (it.pid != null) ? String(it.pid) : '';
                if (!pid) return;
                const contributorCount = contributors.length;
                const dept = extractDepartment(it);
                if (!aggregate[pid]) {
                  aggregate[pid] = { contributorCount, department: dept || '' };
                } else {
                  // keep max contributor count seen (in case of variations) and first non-empty department
                  aggregate[pid].contributorCount = Math.max(aggregate[pid].contributorCount, contributorCount);
                  if (!aggregate[pid].department && dept) {
                    aggregate[pid].department = dept;
                  }
                }
              }
            });
          }

          // ---------------- Paging orchestration -----------------
          const initialStatus = res.getStatus();
          const is2xx = initialStatus >= 200 && initialStatus < 300;
          if (!is2xx) {
            test('Request failed - not aggregating on non-2xx', function () {
              expect(is2xx).to.eql(true);
            });
            return;
          }

          const rb = safeJson(JSON.stringify(res.getBody()));
          const initialItems = rb ? getItemsFromResponseBody(rb) : [];
          considerItems(initialItems);

          // Derive total pages from header 'Page-Count' or 'page-count'
          const pageCountHeader = res.getHeader('Page-Count') || res.getHeader('page-count') || res.getHeader('X-Total-Pages');
          const totalPages = pageCountHeader ? parseInt(pageCountHeader, 10) : 1;

          // Figure out current page from request URL (query param 'page')
          const currentQuery = getQueryObject(req.getUrl());
          const currentPage = parseInt(currentQuery[PAGE_PARAM] || '1', 10) || 1;
          
          // Build a function that fetches page N and aggregates
          function fetchPage(n) {
            return new Promise((resolve) => {
              //console.log("Setting current page to: " + n);
              const nextUrl = setQueryObject(req.getUrl(), { [PAGE_PARAM]: String(n) });
              const reqHeaders = req.getHeaders();
              
              console.log("Setting headers: " + JSON.stringify(reqHeaders));
              setTimeout(async function () {
                console.log("Calling next URL... (" + nextUrl + ")");
                await bru.sendRequest({ url: nextUrl, method: 'GET', headers: reqHeaders }, async function(err, res) {
                  if (err || !res) {
                    console.log("Error fetching page " + n + " (err: " + JSON.stringify(err) + ")");
                    test('Error fetching page ' + n, function () {
                      expect(err).to.eql(null);
                    });
                    return resolve(false);
                  }
                  const ok = res.status >= 200 && res.status < 300;
                  console.log("Next URL response: " + ok);
                  if (!ok) {
                    test('Non-2xx on page ' + n + ' - stop further paging', function () {
                      expect(ok).to.eql(true);
                    });
                    return resolve(false);
                  }
                  const body = res.data;
                  const json = safeJson(body);
                  const items = json ? getItemsFromResponseBody(json) : [];
                  considerItems(items);
                  return resolve(true);
                });
                
              }, RATE_LIMIT_DELAY_MS);
            });
          }

          async function run() {
            console.log("Running...");
            console.log("Total pages found: " + totalPages);
            // If there are more pages, iterate
            for (let p = currentPage + 1; p <= totalPages; p++) {
              const cont = await fetchPage(p);
              if (!cont) break;
            }

            // Prepare output array
            const output = Object.keys(aggregate).map(pid => ({
              pid,
              contributorCount: aggregate[pid].contributorCount,
              department: aggregate[pid].department || ''
            }));

            // Save to environment
            bru.setVar(TARGET_ENV_VAR, JSON.stringify(output));

            console.log(JSON.stringify(output));
            
            // Basic summary tests
            test('Aggregated items have required properties', function () {
              output.forEach(item => {
                expect(item).to.have.property('pid');
                expect(item.pid).to.be.a('string');
                expect(item).to.have.property('contributorCount');
                expect(item.contributorCount).to.be.a('number');
                expect(item).to.have.property('department');
                expect(item.department).to.be.a('string');
              });
            });

            // Optional: Log summary count
            test('Total multi-contributor contracts aggregated', function () {
              expect(output.length).to.be.at.least(0);
            });
          }

          run();
        })();
  actions:
    - type: set-variable
      phase: after-response
      selector:
        expression: ""
        method: jsonq
      variable:
        name: multiContributorContracts
        scope: runtime

settings:
  encodeUrl: true
  timeout: 0
  followRedirects: true
  maxRedirects: 5
  forwardAuthorizationHeader: true
