#!/usr/bin/env node

const https = require('https');

const API_DOC_URL = 'https://foodsharing.de/api/doc';

const EXPECTED_ENDPOINTS = [
  {
    path: '/api/login',
    method: 'post',
    requestRef: '#/components/schemas/LoginRequest',
    responseCodes: ['200', '401', '403', '409']
  },
  {
    path: '/api/users/current/details',
    method: 'get',
    responseRef: '#/components/schemas/ProfileDetails',
    responseCodes: ['200', '401']
  },
  {
    path: '/api/users/{userId}/stores',
    method: 'get',
    responseCodes: ['200', '400', '401', '403', '404']
  },
  {
    path: '/api/stores/{storeId}/pickups',
    method: 'get',
    responseCodes: ['200', '401', '403']
  },
  {
    path: '/api/regions/{regionId}/stores',
    method: 'get',
    responseCodes: ['200', '401', '403']
  },
  {
    path: '/api/stores/{storeId}/details',
    method: 'get',
    responseRef: '#/components/schemas/Store',
    responseCodes: ['200', '401', '403', '404']
  },
  {
    path: '/api/stores/{storeId}/members',
    method: 'get',
    responseCodes: ['200', '401', '403', '404']
  },
  {
    path: '/api/stores/{storeId}/regular-pickups',
    method: 'get',
    responseCodes: ['200', '401', '403', '404']
  },
  {
    path: '/api/users/{userId}/pickups/registered',
    method: 'get',
    responseCodes: ['200', '401', '403']
  },
  {
    path: '/api/stores/{storeId}/pickups/{pickupDate}/eligibility',
    method: 'get',
    responseCodes: ['200', '401']
  },
  {
    path: '/api/stores/{storeId}/pickups/{pickupDate}/users/current',
    method: 'post',
    responseCodes: ['200', '401', '403']
  },
  {
    path: '/api/conversations',
    method: 'get',
    responseCodes: ['200', '401']
  }
];

function fetchText(url) {
  return new Promise((resolve, reject) => {
    https
      .get(url, (res) => {
        if (res.statusCode && res.statusCode >= 400) {
          reject(new Error(`HTTP ${res.statusCode} for ${url}`));
          res.resume();
          return;
        }
        let data = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
          data += chunk;
        });
        res.on('end', () => resolve(data));
      })
      .on('error', reject);
  });
}

function extractSpec(html) {
  const match = html.match(/<script id="swagger-data" type="application\/json">([\s\S]*?)<\/script>/);
  if (!match) {
    throw new Error('Swagger spec konnte in /api/doc nicht gefunden werden.');
  }
  const payload = JSON.parse(match[1]);
  if (!payload?.spec?.paths) {
    throw new Error('OpenAPI-Spec in /api/doc ist unvollständig.');
  }
  return payload.spec;
}

function schemaRefFromResponse(operation) {
  return operation?.responses?.['200']?.content?.['application/json']?.schema?.$ref || null;
}

function schemaRefFromRequest(operation) {
  return operation?.requestBody?.content?.['application/json']?.schema?.$ref || null;
}

function validateOperation(spec, expectation) {
  const operation = spec.paths?.[expectation.path]?.[expectation.method];
  if (!operation) {
    return [`Fehlt: ${expectation.method.toUpperCase()} ${expectation.path}`];
  }

  const issues = [];

  if (expectation.requestRef) {
    const actualRef = schemaRefFromRequest(operation);
    if (actualRef !== expectation.requestRef) {
      issues.push(
        `Unerwartetes Request-Schema bei ${expectation.method.toUpperCase()} ${expectation.path}: ${actualRef || 'keins'}`
      );
    }
  }

  if (expectation.responseRef) {
    const actualRef = schemaRefFromResponse(operation);
    if (actualRef !== expectation.responseRef) {
      issues.push(
        `Unerwartetes 200-Schema bei ${expectation.method.toUpperCase()} ${expectation.path}: ${actualRef || 'keins'}`
      );
    }
  }

  const actualCodes = Object.keys(operation.responses || {}).sort();
  const expectedCodes = [...expectation.responseCodes].sort();
  if (actualCodes.join(',') !== expectedCodes.join(',')) {
    issues.push(
      `Unerwartete Response-Codes bei ${expectation.method.toUpperCase()} ${expectation.path}: erwartet ${expectedCodes.join(
        ','
      )}, bekommen ${actualCodes.join(',')}`
    );
  }

  return issues;
}

async function main() {
  const html = await fetchText(API_DOC_URL);
  const spec = extractSpec(html);
  const issues = EXPECTED_ENDPOINTS.flatMap((endpoint) => validateOperation(spec, endpoint));

  if (issues.length > 0) {
    console.error('Foodsharing-API-Doku stimmt nicht mit den erwarteten Endpunkten ueberein:');
    issues.forEach((issue) => console.error(`- ${issue}`));
    process.exit(1);
  }

  console.log(`Geprueft: ${EXPECTED_ENDPOINTS.length} Foodsharing-Endpunkte laut ${API_DOC_URL}`);
  EXPECTED_ENDPOINTS.forEach((endpoint) => {
    console.log(`- ${endpoint.method.toUpperCase()} ${endpoint.path}`);
  });
}

main().catch((error) => {
  console.error(error.message);
  process.exit(1);
});
