import { OperatorTypes } from '@unito/integration-api';
import express from 'express';
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import extractFilters from '../../src/middlewares/filters.js';

describe('filters middleware', () => {
  it('properly parse operators', () => {
    Object.values(OperatorTypes).forEach(operator => {
      const request = {
        query: { filter: `aKey${operator}value` },
      } as express.Request<any, object, object, { filter: string }>;

      const response = { locals: {} } as express.Response;

      extractFilters(request, response, () => {});

      assert.deepEqual(response.locals, {
        filters: [{ field: 'aKey', operator, values: ['value'] }],
      });
    });

    const request = {
      query: { filter: `aKey!!` },
    } as express.Request<any, object, object, { filter: string }>;

    const response = { locals: {} } as express.Response;

    extractFilters(request, response, () => {});

    assert.deepEqual(response.locals, {
      filters: [{ field: 'aKey', operator: OperatorTypes.IS_NULL, values: [] }],
    });
  });

  it('decodes URI components', () => {
    const request = { query: { filter: 'status=foo%2Cbar%21%21%2C%3Fbaz%3D!%3Equx' } } as express.Request<
      any,
      object,
      object,
      { filter: string }
    >;
    const response = { locals: {} } as express.Response;

    extractFilters(request, response, () => {});

    assert.deepEqual(response.locals, {
      filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
    });
  });

  it('parses multi-value filters separated by pipe', () => {
    const request = {
      query: { filter: 'status=/statuses/Done|/statuses/InProgress' },
    } as express.Request<any, object, object, { filter: string }>;
    const response = { locals: {} } as express.Response;

    extractFilters(request, response, () => {});

    assert.deepEqual(response.locals, {
      filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['/statuses/Done', '/statuses/InProgress'] }],
    });
  });

  it('preserves base64 padding (=) in filter values and does not lose subsequent pipe-separated values', () => {
    // Base64 values end with == which contains the = operator character.
    // split('=', 2) incorrectly truncates after the second = in the string,
    // discarding the | separator and subsequent values.
    const request = {
      query: {
        filter:
          'category=/choices/QnVzaW5lc3MgU3lzdGVtOi06QnVzaW5lc3MgU3lzdGVt|/choices/QXBwbGljYXRpb246LTpBcHBsaWNhdGlvbg==,' +
          'subcategory=/choices/U0FQOi06QnVzaW5lc3MgU3lzdGVtX1NBUA==|/choices/UmVkd29vZCBSTUo6LTpyZWR3b29kIHJtag==',
      },
    } as express.Request<any, object, object, { filter: string }>;
    const response = { locals: {} } as express.Response;

    extractFilters(request, response, () => {});

    assert.deepEqual(response.locals, {
      filters: [
        {
          field: 'category',
          operator: OperatorTypes.EQUAL,
          values: [
            '/choices/QnVzaW5lc3MgU3lzdGVtOi06QnVzaW5lc3MgU3lzdGVt',
            '/choices/QXBwbGljYXRpb246LTpBcHBsaWNhdGlvbg==',
          ],
        },
        {
          field: 'subcategory',
          operator: OperatorTypes.EQUAL,
          values: ['/choices/U0FQOi06QnVzaW5lc3MgU3lzdGVtX1NBUA==', '/choices/UmVkd29vZCBSTUo6LTpyZWR3b29kIHJtag=='],
        },
      ],
    });
  });

  it('matches the earliest operator even when operator characters appear in the value', () => {
    const request = {
      query: { filter: 'description=contains>=text' },
    } as express.Request<any, object, object, { filter: string }>;
    const response = { locals: {} } as express.Response;

    extractFilters(request, response, () => {});

    assert.deepEqual(response.locals, {
      filters: [{ field: 'description', operator: OperatorTypes.EQUAL, values: ['contains>=text'] }],
    });
  });

  it('no data', () => {
    const request = { query: {} } as express.Request;
    const response = { locals: {} } as express.Response;

    extractFilters(request, response, () => {});

    assert.deepEqual(response.locals, {
      filters: [],
    });
  });
});
