import { $DataFormats } from 'src/app/models/sas/editors-getdata.model'
import { DQData, SASParam } from 'src/app/models/TableData'
import { DcValidator } from '../dc-validator'
import { Col } from '../models/col.model'
import { DQRule } from '../models/dq-rules.model'

describe('DC Validator', () => {
  it('should create an instance of validator with correct rules', () => {
    const sasparams: SASParam = example_sasparams
    const cols: Col[] = example_cols
    const dqRules: DQRule[] = example_dqRules
    const dqData: DQData[] = example_dqData
    const $dataFormats: $DataFormats = example_dataformats

    const dcValidator: DcValidator = new DcValidator(
      sasparams,
      $dataFormats,
      cols,
      dqRules,
      dqData
    )

    // Check if COLS merged with FORMATS
    expect(cols[0].TYPE).toEqual('char')

    // Get all
    const validationRules = dcValidator.getRules()
    expect(validationRules).toHaveSize(
      JSON.parse(`[${example_COLTYPE}]`).length
    )

    // Get col with notnull validation
    const someNumRule = dcValidator.getRule('SOME_NUM')
    // Check allowEmpty - it is notnull rule so allowEmpty should be false
    expect(someNumRule?.allowEmpty).toBeFalse()

    // SAS sends semantic date/time/datetime column types; the frontend maps them
    // to the HOT 17 Intl cell-type names (see mapIntlCellTypes). The cell display
    // is the raw stored ISO string (no locale/format meta) — see
    // registerIntlCellTypes. The deprecated correctFormat + moment format strings
    // are no longer sent in the payload.
    const someDateRule = dcValidator.getRule('SOME_DATE')
    expect(someDateRule?.correctFormat).toBeUndefined()
    expect(someDateRule?.type).toBe('intl-date')

    const someDatetimeRule = dcValidator.getRule('SOME_DATETIME')
    expect(someDatetimeRule?.type).toBe('intl-datetime')
    expect(someDatetimeRule?.correctFormat).toBeUndefined()

    const someTimeRule = dcValidator.getRule('SOME_TIME')
    expect(someTimeRule?.type).toBe('intl-time')
    expect(someTimeRule?.correctFormat).toBeUndefined()

    // Get col with dropdown, check if merging sas data is correct
    const someDropdownRule = dcValidator.getRule('SOME_DROPDOWN')
    // Check  source
    expect(someDropdownRule?.source).toHaveSize(4)
    // Check description
    expect(someDropdownRule?.desc).toEqual('dropdown_desc')
    // Check length
    expect(someDropdownRule?.length).toEqual(128)
    // Check strict - it is softselect so strict should be false
    expect(someDropdownRule?.strict).toBeFalse()

    // Get col with dropdown with 'HARDSELECT' rule
    const someDropdownHardRule = dcValidator.getRule('SOME_DROPDOWN_HARD')
    // Check strict - it is hardselect so strict should be true
    expect(someDropdownHardRule?.strict).toBeTrue()
  })

  it('should create an instance of validator and execute its functions', () => {
    const sasparams: SASParam = example_sasparams
    const cols: Col[] = example_cols
    const dqRules: DQRule[] = example_dqRules
    const dqData: DQData[] = example_dqData
    const $dataFormats: $DataFormats = example_dataformats

    const dcValidator: DcValidator = new DcValidator(
      sasparams,
      $dataFormats,
      cols,
      dqRules,
      dqData
    )

    // Get one
    const someNumRules = dcValidator.getRule('SOME_NUM')
    expect(someNumRules?.data).toEqual('SOME_NUM')

    // Update one
    dcValidator.updateRule('SOME_NUM', { desc: 'updated_desc' })
    const someNumUpdated = dcValidator.getRule('SOME_NUM')
    expect(someNumUpdated?.desc).toEqual('updated_desc')

    // Remove one
    dcValidator.removeRule('SOME_TIME')
    expect(dcValidator.getRule('SOME_TIME')).toBeUndefined()

    // Test data quality functions
    expect(dcValidator.getDqDetails()).toHaveSize(dqRules.length)
    expect(dcValidator.getDqDetails('non_existant')).toHaveSize(0)
    expect(dcValidator.getDqDetails('SOME_NUM')).toHaveSize(2)
    expect(dcValidator.isDqCol('SOME_NUM')).toBeTrue()
    expect(dcValidator.isDqCol('non_existant')).toBeFalse()
    expect(
      dcValidator.getDqDropdownSource(dcValidator.getRule('SOME_DROPDOWN')!)
    ).toEqual([
      'Option 1',
      'Option 2',
      'Option 3',
      'This is a long option.  This option is very long.  It is optional, though.'
    ])
  })

  it('should test hot validator', () => {
    const sasparams: SASParam = example_sasparams
    const cols: Col[] = example_cols
    const dqRules: DQRule[] = example_dqRules
    const dqData: DQData[] = example_dqData
    const $dataFormats: $DataFormats = example_dataformats

    const dcValidator: DcValidator = new DcValidator(
      sasparams,
      $dataFormats,
      cols,
      dqRules,
      dqData
    )

    // NOTNULL Validation
    const someNumRule = dcValidator.getRule('SOME_NUM')

    dcValidator.executeHotValidator(someNumRule!, 2, (valid: boolean) => {
      expect(valid).toBeTrue()
    })
    dcValidator.executeHotValidator(someNumRule!, null, (valid: boolean) => {
      expect(valid).toBeFalse()
    })
    dcValidator.executeHotValidator(someNumRule!, '', (valid: boolean) => {
      expect(valid).toBeFalse()
    })
    dcValidator.executeHotValidator(someNumRule!, 'ss', (valid: boolean) => {
      expect(valid).toBeFalse()
    })
    //Special missings
    dcValidator.executeHotValidator(someNumRule!, 's', (valid: boolean) => {
      expect(valid).toBeTrue()
    })
    dcValidator.executeHotValidator(someNumRule!, '.s', (valid: boolean) => {
      expect(valid).toBeTrue()
    })
    dcValidator.executeHotValidator(someNumRule!, '.', (valid: boolean) => {
      expect(valid).toBeTrue()
    })
    dcValidator.executeHotValidator(someNumRule!, '..', (valid: boolean) => {
      expect(valid).toBeFalse()
    })
    dcValidator.executeHotValidator(someNumRule!, '._', (valid: boolean) => {
      expect(valid).toBeTrue()
    })

    // MINVAL, MAXVAL Validation
    const shortNumRule = dcValidator.getRule('SOME_SHORTNUM')

    dcValidator.executeHotValidator(shortNumRule!, 2, (valid: boolean) => {
      expect(valid).toBeTrue()
    })
    dcValidator.executeHotValidator(shortNumRule!, 1, (valid: boolean) => {
      expect(valid).toBeFalse()
    })
    dcValidator.executeHotValidator(shortNumRule!, 4, (valid: boolean) => {
      expect(valid).toBeFalse()
    })
    dcValidator.executeHotValidator(shortNumRule!, 's', (valid: boolean) => {
      expect(valid).toBeFalse() // Special missings are lowest numbers, if any MINVAL is set, special missing is always lower
    })

    // CASE validation
    const someCharRule = dcValidator.getRule('SOME_CHAR')

    dcValidator.executeHotValidator(
      someCharRule!,
      'UPPER',
      (valid: boolean) => {
        expect(valid).toBeTrue()
      }
    )
    dcValidator.executeHotValidator(
      someCharRule!,
      'lower',
      (valid: boolean) => {
        expect(valid).toBeFalse()
      }
    )
    dcValidator.executeHotValidator(
      someCharRule!,
      undefined,
      (valid: boolean) => {
        expect(valid).toBeTrue()
      }
    )
    dcValidator.executeHotValidator(someCharRule!, null, (valid: boolean) => {
      expect(valid).toBeTrue()
    })

    const someCharLowRule = dcValidator.getRule('SOME_CHAR_LOW')

    dcValidator.executeHotValidator(
      someCharLowRule!,
      'lower',
      (valid: boolean) => {
        expect(valid).toBeTrue()
      }
    )

    dcValidator.executeHotValidator(
      someCharLowRule!,
      'UPPER',
      (valid: boolean) => {
        expect(valid).toBeFalse()
      }
    )

    const someCharAnyRule = dcValidator.getRule('SOME_CHAR_ANY')

    dcValidator.executeHotValidator(
      someCharAnyRule!,
      'any',
      (valid: boolean) => {
        expect(valid).toBeTrue()
      }
    )

    dcValidator.executeHotValidator(
      someCharAnyRule!,
      null,
      (valid: boolean) => {
        expect(valid).toBeTrue()
      }
    )

    dcValidator.executeHotValidator(
      someCharAnyRule!,
      undefined,
      (valid: boolean) => {
        expect(valid).toBeTrue()
      }
    )
  })

  it('should apply READONLY, HIDDEN, NUMBER_FORMAT and ROUND rules', () => {
    const dcValidator: DcValidator = new DcValidator(
      example_sasparams,
      example_dataformats,
      example_cols,
      example_dqRules,
      example_dqData
    )

    // READONLY -> column rendered read-only
    expect(dcValidator.getRule('SOME_BESTNUM')?.readOnly).toBeTrue()

    // NUMBER_FORMAT -> a function renderer is assigned and numbro numericFormat
    // is cleared so it can't override the Intl currency/percent rendering
    expect(typeof dcValidator.getRule('SOME_BESTNUM')?.renderer).toEqual(
      'function'
    )
    expect(dcValidator.getRule('SOME_BESTNUM')?.numericFormat).toBeUndefined()

    // HIDDEN -> column index added to hidden columns
    const rules = dcValidator.getRules()
    const hiddenIndex = rules.findIndex(
      (rule) => rule.data === 'PRIMARY_KEY_FIELD'
    )
    expect(dcValidator.getHiddenColumns()).toContain(hiddenIndex)

    // ROUND -> num_digits returned for the column, undefined otherwise
    expect(dcValidator.getRoundDigits('SOME_SHORTNUM')).toEqual(2)
    expect(dcValidator.getRoundDigits('SOME_NUM')).toBeUndefined()
  })
})

const example_dqData = [
  {
    BASE_COL: 'SOME_DROPDOWN',
    RULE_VALUE: 'SOME_DROPDOWN',
    RULE_DATA: 'Option 1',
    SELECTBOX_ORDER: 1
  },
  {
    BASE_COL: 'SOME_DROPDOWN',
    RULE_VALUE: 'SOME_DROPDOWN',
    RULE_DATA: 'Option 2',
    SELECTBOX_ORDER: 2
  },
  {
    BASE_COL: 'SOME_DROPDOWN',
    RULE_VALUE: 'SOME_DROPDOWN',
    RULE_DATA: 'Option 3',
    SELECTBOX_ORDER: 2
  },
  {
    BASE_COL: 'SOME_DROPDOWN',
    RULE_VALUE: 'SOME_DROPDOWN',
    RULE_DATA:
      'This is a long option.  This option is very long.  It is optional, though.',
    SELECTBOX_ORDER: 3
  }
]

const example_dqRules: any = [
  {
    BASE_COL: 'SOME_NUM',
    RULE_TYPE: 'NOTNULL',
    RULE_VALUE: ' ',
    X: 0
  },
  {
    BASE_COL: 'SOME_NUM',
    RULE_TYPE: 'HARDSELECT_HOOK',
    RULE_VALUE: 'services/validations/mpe_x_test.some_num',
    X: 0
  },
  {
    BASE_COL: 'SOME_SHORTNUM',
    RULE_TYPE: 'MINVAL',
    RULE_VALUE: '2',
    X: 0
  },
  {
    BASE_COL: 'SOME_DROPDOWN_HARD', // It contains both HARD and SOFTSELECT rules, HARDSELECT has the priority
    RULE_TYPE: 'HARDSELECT',
    RULE_VALUE: ' ',
    X: 0
  },
  {
    BASE_COL: 'SOME_DROPDOWN_HARD', // It contains both HARD and SOFTSELECT rules, HARDSELECT has the priority
    RULE_TYPE: 'SOFTSELECT',
    RULE_VALUE: ' ',
    X: 0
  },
  {
    BASE_COL: 'SOME_SHORTNUM',
    RULE_TYPE: 'MAXVAL',
    RULE_VALUE: '3',
    X: 0
  },
  {
    BASE_COL: 'SOME_CHAR',
    RULE_TYPE: 'CASE',
    RULE_VALUE: 'UPCASE',
    X: 0
  },
  {
    BASE_COL: 'SOME_CHAR_LOW',
    RULE_TYPE: 'CASE',
    RULE_VALUE: 'LOWCASE',
    X: 0
  },
  {
    BASE_COL: 'SOME_BESTNUM',
    RULE_TYPE: 'READONLY',
    RULE_VALUE: '7',
    X: 0
  },
  {
    BASE_COL: 'SOME_BESTNUM',
    RULE_TYPE: 'NUMBER_FORMAT',
    RULE_VALUE: '{"minimumFractionDigits":2}',
    X: 0
  },
  {
    BASE_COL: 'PRIMARY_KEY_FIELD',
    RULE_TYPE: 'HIDDEN',
    RULE_VALUE: '99',
    X: 0
  },
  {
    BASE_COL: 'SOME_SHORTNUM',
    RULE_TYPE: 'ROUND',
    RULE_VALUE: '2',
    X: 0
  }
]

const example_cols = [
  {
    CLS_RULE: 'READ',
    DDTYPE: 'CHARACTER',
    DESC: 'dropdown_desc',
    TYPE: '',
    FMTNAME: '',
    LABEL: 'SOME_DROPDOWN',
    LONGDESC: '',
    MEMLABEL: '',
    NAME: 'SOME_DROPDOWN',
    VARNUM: 3
  },
  {
    CLS_RULE: 'READ',
    DDTYPE: 'NUMERIC',
    DESC: '',
    TYPE: '',
    FMTNAME: '',
    LABEL: 'SOME_NUM',
    LONGDESC: '',
    MEMLABEL: '',
    NAME: 'SOME_NUM',
    VARNUM: 4
  }
]

const example_COLTYPE = `
{
    "data":"_____DELETE__THIS__RECORD_____",
    "type":"dropdown",
    "source":[
        "No",
        "Yes"
    ]
},
{
    "data":"PRIMARY_KEY_FIELD",
    "type":"numeric",
    "format":"0"
},
{
    "data":"SOME_CHAR"
},
{
	"data":"SOME_CHAR_LOW"
},
{
	"data":"SOME_CHAR_ANY"
},
{
    "data":"SOME_DROPDOWN"
},
{
  "data":"SOME_DROPDOWN_HARD"
},
{
    "data":"SOME_NUM",
    "type":"numeric",
    "format":"0"
},
{
    "data":"SOME_DATE",
    "type":"date"
},
{
    "data":"SOME_DATETIME",
    "type":"datetime"
},
{
    "data":"SOME_TIME",
    "type":"time"
},
{
    "data":"SOME_SHORTNUM",
    "type":"numeric",
    "format":"0"
},
{
    "data":"SOME_BESTNUM",
    "type":"numeric",
    "format":"0"
}`

const example_sasparams = {
  CLS_FLAG: 0,
  COLHEADERS: 'head1 head2',
  COLTYPE: example_COLTYPE,
  DTTMVARS: 'dttm vars',
  DTVARS: 'dt vars',
  FILTER_TEXT: 'filter text',
  LOADTYPE: 'load type',
  PK: 'primary key',
  PKCNT: 0,
  RK_FLAG: 0,
  TMVARS: 'string'
}

const example_dataformats = {
  vars: {
    _____DELETE__THIS__RECORD_____: {
      format: '$3.',
      label: '_____DELETE__THIS__RECORD_____',
      length: '3',
      type: 'char'
    },
    PRIMARY_KEY_FIELD: {
      format: 'best.',
      label: 'PRIMARY_KEY_FIELD',
      length: '8',
      type: 'num'
    },
    SOME_CHAR: {
      format: '$32767.',
      label: 'SOME_CHAR',
      length: '32767',
      type: 'char'
    },
    SOME_DROPDOWN: {
      format: '$128.',
      label: 'SOME_DROPDOWN',
      length: '128',
      type: 'char'
    },
    SOME_NUM: {
      format: 'best.',
      label: 'SOME_NUM',
      length: '8',
      type: 'num'
    },
    SOME_DATE: {
      format: '$200.',
      label: 'SOME_DATE',
      length: '200',
      type: 'char'
    },
    SOME_DATETIME: {
      format: '$200.',
      label: 'SOME_DATETIME',
      length: '200',
      type: 'char'
    },
    SOME_TIME: {
      format: '$200.',
      label: 'SOME_TIME',
      length: '200',
      type: 'char'
    },
    SOME_SHORTNUM: {
      format: 'best.',
      label: 'SOME_SHORTNUM',
      length: '4',
      type: 'num'
    },
    SOME_BESTNUM: {
      format: 'BEST.',
      label: 'SOME_BESTNUM',
      length: '8',
      type: 'num'
    }
  }
}
