import { Collection, Storage, Dictionary } from '@freearhey/core'
import { DataLoaderData } from '../../types/dataLoader'
import { ValidatorError } from '../../types/validator'
import { DataLoader } from '../../core/dataLoader'
import { DATA_DIR } from '../../constants'
import chalk from 'chalk'
import {
  BlocklistRecord,
  Subdivision,
  Category,
  Language,
  Timezone,
  Channel,
  Country,
  Region,
  Feed
} from '../../models'
import {
  BlocklistRecordValidator,
  SubdivisionValidator,
  CategoryValidator,
  LanguageValidator,
  TimezoneValidator,
  ChannelValidator,
  CountryValidator,
  RegionValidator,
  FeedValidator
} from '../../validators'

let totalErrors = 0

async function main() {
  const dataStorage = new Storage(DATA_DIR)
  const dataLoader = new DataLoader({ storage: dataStorage })
  const data = await dataLoader.load()

  validateChannels(data)
  validateFeeds(data)
  validateRegions(data)
  validateBlocklist(data)
  validateCategories(data)
  validateCountries(data)
  validateSubdivisions(data)
  validateLanguages(data)
  validateTimezones(data)

  if (totalErrors > 0) {
    console.log(chalk.red(`\r\n${totalErrors} error(s)`))
    process.exit(1)
  }
}

main()

function validateChannels(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.channels, ['id']).forEach((channel: Channel) => {
    errors.add({
      line: channel.getLine(),
      message: `channel with id "${channel.id}" already exists`
    })
  })

  const validator = new ChannelValidator({ data })
  data.channels.forEach((channel: Channel) => {
    errors = errors.concat(validator.validate(channel))
  })

  if (errors.count()) displayErrors('channels.csv', errors)

  totalErrors += errors.count()
}

function validateFeeds(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.feeds, ['channelId', 'id']).forEach((feed: Feed) => {
    errors.add({
      line: feed.getLine(),
      message: `feed with channel "${feed.channelId}" and id "${feed.id}" already exists`
    })
  })

  const validator = new FeedValidator({ data })
  data.feeds.forEach((feed: Feed) => {
    errors = errors.concat(validator.validate(feed))
  })

  if (errors.count()) displayErrors('feeds.csv', errors)

  totalErrors += errors.count()
}

function validateRegions(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.regions, ['code']).forEach((region: Region) => {
    errors.add({
      line: region.getLine(),
      message: `region with code "${region.code}" already exists`
    })
  })

  const validator = new RegionValidator({ data })
  data.regions.forEach((region: Region) => {
    errors = errors.concat(validator.validate(region))
  })

  if (errors.count()) displayErrors('regions.csv', errors)

  totalErrors += errors.count()
}

function validateBlocklist(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.blocklistRecords, ['channelId', 'ref']).forEach(
    (blocklistRecord: BlocklistRecord) => {
      errors.add({
        line: blocklistRecord.getLine(),
        message: `blocklist record with channel "${blocklistRecord.channelId}" and ref "${blocklistRecord.ref}" already exists`
      })
    }
  )

  const validator = new BlocklistRecordValidator({ data })
  data.blocklistRecords.forEach((blocklistRecord: BlocklistRecord) => {
    errors = errors.concat(validator.validate(blocklistRecord))
  })

  if (errors.count()) displayErrors('blocklist.csv', errors)

  totalErrors += errors.count()
}

function validateCategories(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.categories, ['id']).forEach((category: Category) => {
    errors.add({
      line: category.getLine(),
      message: `category with id "${category.id}" already exists`
    })
  })

  const validator = new CategoryValidator({ data })
  data.categories.forEach((category: Category) => {
    errors = errors.concat(validator.validate(category))
  })

  if (errors.count()) displayErrors('categories.csv', errors)

  totalErrors += errors.count()
}

function validateCountries(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.countries, ['code']).forEach((country: Country) => {
    errors.add({
      line: country.getLine(),
      message: `country with code "${country.code}" already exists`
    })
  })

  const validator = new CountryValidator({ data })
  data.countries.forEach((country: Country) => {
    errors = errors.concat(validator.validate(country))
  })

  if (errors.count()) displayErrors('countries.csv', errors)

  totalErrors += errors.count()
}

function validateSubdivisions(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.subdivisions, ['code']).forEach((subdivision: Subdivision) => {
    errors.add({
      line: subdivision.getLine(),
      message: `subdivision with code "${subdivision.code}" already exists`
    })
  })

  const validator = new SubdivisionValidator({ data })
  data.subdivisions.forEach((subdivision: Subdivision) => {
    errors = errors.concat(validator.validate(subdivision))
  })

  if (errors.count()) displayErrors('subdivisions.csv', errors)

  totalErrors += errors.count()
}

function validateLanguages(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.languages, ['code']).forEach((language: Language) => {
    errors.add({
      line: language.getLine(),
      message: `language with code "${language.code}" already exists`
    })
  })

  const validator = new LanguageValidator({ data })
  data.languages.forEach((language: Language) => {
    errors = errors.concat(validator.validate(language))
  })

  if (errors.count()) displayErrors('languages.csv', errors)

  totalErrors += errors.count()
}

function validateTimezones(data: DataLoaderData) {
  let errors = new Collection()

  findDuplicatesBy(data.timezones, ['id']).forEach((timezone: Timezone) => {
    errors.add({
      line: timezone.getLine(),
      message: `timezone with id "${timezone.id}" already exists`
    })
  })

  const validator = new TimezoneValidator({ data })
  data.timezones.forEach((timezone: Timezone) => {
    errors = errors.concat(validator.validate(timezone))
  })

  if (errors.count()) displayErrors('timezones.csv', errors)

  totalErrors += errors.count()
}

function findDuplicatesBy(items: Collection, keys: string[]) {
  const duplicates = new Collection()
  const buffer = new Dictionary()

  items.forEach((item: { [key: string]: string | number }) => {
    const normId = keys.map(key => item[key].toString().toLowerCase()).join()
    if (buffer.has(normId)) {
      duplicates.add(item)
    }

    buffer.set(normId, true)
  })

  return duplicates
}

function displayErrors(filepath: string, errors: Collection) {
  console.log(`\r\n${chalk.underline(filepath)}`)

  errors.forEach((error: ValidatorError) => {
    const position = error.line.toString().padEnd(6, ' ')
    console.log(` ${chalk.gray(position) + error.message}`)
  })
}
