import { VaFilterService } from './va-filter.service'
import { VaMessage } from './va-messaging.service'

/**
 * Minimal stand-in for HelperService.convertJsDateToSasDate using the real
 * epoch math, so date/datetime assertions use the actual SAS internal values
 * without pulling in HelperService's dependency graph (SasService etc.).
 */
class HelperServiceStub {
  convertJsDateToSasDate(jsDate: Date, unit = 'days'): number {
    const ms = Date.UTC(
      jsDate.getFullYear(),
      jsDate.getMonth(),
      jsDate.getDate(),
      jsDate.getHours(),
      jsDate.getMinutes(),
      jsDate.getSeconds()
    )
    const sasMs = ms + 315619200000
    return unit === 'days'
      ? Math.floor(Math.abs(sasMs / 86400000))
      : sasMs / 1000
  }
}

function msg(parameters: any[]): VaMessage {
  return {
    resultName: 'r',
    rowCount: 0,
    data: [],
    columns: [],
    parameters
  } as VaMessage
}

describe('VaFilterService', () => {
  let service: VaFilterService

  // DC column metadata used by the metadata path. cellValidation[0] is the
  // delete column and must be skipped by buildLabelToVarMap.
  const cellValidation = [
    { data: 'DEL' },
    { data: 'SOME_CHAR' },
    { data: 'SOME_NUM' },
    { data: 'SOME_TIME' },
    { data: 'SOME_DATE' },
    { data: 'SOME_DATETIME' }
  ]
  const columnHeader = [
    'Delete?',
    'SOME_CHAR',
    'SOME_NUM',
    'SOME_TIME',
    'SOME_DATE',
    'SOME_DATETIME'
  ]
  const cols = [
    { NAME: 'SOME_CHAR', TYPE: 'char', DDTYPE: 'CHAR' },
    { NAME: 'SOME_NUM', TYPE: 'num', DDTYPE: 'NUMERIC' },
    { NAME: 'SOME_TIME', TYPE: 'num', DDTYPE: 'TIME' },
    { NAME: 'SOME_DATE', TYPE: 'num', DDTYPE: 'DATE' },
    { NAME: 'SOME_DATETIME', TYPE: 'num', DDTYPE: 'DATETIME' }
  ]

  beforeEach(() => {
    service = new VaFilterService(new HelperServiceStub() as any)
  })

  const labelToVar = () =>
    service.buildLabelToVarMap(cellValidation, columnHeader)

  describe('buildLabelToVarMap', () => {
    it('maps header and var name (case-insensitive), skipping the delete column', () => {
      const map = labelToVar()
      expect(map.get('some_char')).toBe('SOME_CHAR')
      expect(map.get('some_num')).toBe('SOME_NUM')
      expect(map.get('del')).toBeUndefined()
      expect(map.get('delete?')).toBeUndefined()
    })
  })

  describe('buildClauses (metadata path)', () => {
    const build = (parameters: any[]) =>
      service.buildClauses(msg(parameters), labelToVar(), cols)

    it('builds a char equality clause, single-quote escaped', () => {
      expect(build([{ label: 'SOME_CHAR', value: "O'Brien" }])).toEqual([
        {
          GROUP_LOGIC: 'AND',
          SUBGROUP_LOGIC: 'AND',
          SUBGROUP_ID: 0,
          VARIABLE_NM: 'SOME_CHAR',
          OPERATOR_NM: '=',
          RAW_VALUE: "'O''Brien'"
        }
      ])
    })

    it('builds an IN clause for multiple values', () => {
      const [clause] = build([{ label: 'SOME_CHAR', value: ['A', 'B'] }])
      expect(clause.OPERATOR_NM).toBe('IN')
      expect(clause.RAW_VALUE).toBe("('A','B')")
    })

    it('passes numeric values raw and drops non-numeric (injection guard)', () => {
      expect(build([{ label: 'SOME_NUM', value: '10' }])[0].RAW_VALUE).toBe(
        '10'
      )
      expect(build([{ label: 'SOME_NUM', value: 'abc' }])).toEqual([])
    })

    it('converts temporal values to SAS internal numbers', () => {
      expect(
        build([{ label: 'SOME_TIME', value: '0:00:02' }])[0].RAW_VALUE
      ).toBe('2')
      expect(
        build([{ label: 'SOME_DATE', value: '01JUL1960' }])[0].RAW_VALUE
      ).toBe('182')
      expect(
        build([{ label: 'SOME_DATETIME', value: '01JAN1960:00:06:05' }])[0]
          .RAW_VALUE
      ).toBe('365')
    })

    it('matches the column by label case-insensitively', () => {
      expect(build([{ label: 'some_char', value: 'x' }])[0].VARIABLE_NM).toBe(
        'SOME_CHAR'
      )
    })

    it('skips params that are empty or do not map to a column', () => {
      expect(build([{ label: 'SOME_CHAR', value: '' }])).toEqual([])
      expect(build([{ label: 'NOT_A_COLUMN', value: 'x' }])).toEqual([])
    })
  })

  describe('buildInitialClauses (no metadata, infer kind from value)', () => {
    it('uses the VA label as the column and infers kinds by value shape', () => {
      const clauses = service.buildInitialClauses(
        msg([
          { name: 'pr1', label: 'SOME_CHAR', dataType: 'string', value: 'x' },
          { name: 'pr2', label: 'SOME_NUM', dataType: 'number', value: '10' },
          { name: 'pr3', label: 'SOME_TIME', value: '0:00:02' },
          { name: 'pr4', label: 'SOME_DATE', value: '01JUL1960' },
          { name: 'pr5', label: 'SOME_DATETIME', value: '01JAN1960:00:06:05' }
        ])
      )
      expect(clauses.map((c) => [c.VARIABLE_NM, c.RAW_VALUE])).toEqual([
        ['SOME_CHAR', "'x'"],
        ['SOME_NUM', '10'],
        ['SOME_TIME', '2'],
        ['SOME_DATE', '182'],
        ['SOME_DATETIME', '365']
      ])
    })
  })

  describe('signature', () => {
    it('is case-insensitive on the column name (no reconcile reload)', () => {
      const a = [
        { VARIABLE_NM: 'some_char', OPERATOR_NM: '=', RAW_VALUE: "'x'" }
      ] as any
      const b = [
        { VARIABLE_NM: 'SOME_CHAR', OPERATOR_NM: '=', RAW_VALUE: "'x'" }
      ] as any
      expect(service.signature(a)).toBe(service.signature(b))
    })

    it('changes when a value changes', () => {
      const a = [{ VARIABLE_NM: 'SOME_CHAR', RAW_VALUE: "'x'" }] as any
      const b = [{ VARIABLE_NM: 'SOME_CHAR', RAW_VALUE: "'y'" }] as any
      expect(service.signature(a)).not.toBe(service.signature(b))
    })
  })
})
