import { Component, ViewEncapsulation } from '@angular/core'
import { Location } from '@angular/common'
import { globals } from '../_globals'
import * as d3Viz from 'd3-graphviz'
import { ActivatedRoute, Router } from '@angular/router'
import { EventService } from '../services/event.service'
import { HelperService } from '../services/helper.service'
import { SasService } from '../services/sas.service'
import * as saveSvg from 'save-svg-as-png'
import { LoggerService } from '../services/logger.service'
import { LicenceService } from '../services/licence.service'
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
const moment = require('moment')

@Component({
  selector: 'app-lineage',
  styleUrls: ['./lineage.component.scss'],
  templateUrl: './lineage.component.html',
  host: {
    class: 'content-container'
  },
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class LineageComponent {
  public switchFlag: boolean = false
  public tableFlag: boolean = true

  public forwardLineage: boolean = false
  public flatdata: Array<any> = []

  public graphLoading: boolean = false
  public graphRendering: boolean = false
  public graphContainer: boolean = false
  public vizInput = ''

  public librariesPaging: boolean = false
  public libraryTablesRef: string = ''
  public libraryList: Array<any> | undefined
  public librariesSearch: string = ''
  public tableColumnsRef: string = ''
  public tablesList: Array<any> | undefined
  public columnsList: Array<any> | undefined

  public lineageTableName: string = ''
  public lineageColumnName: string = ''

  public lib: string | null = null
  public table: string | undefined
  public column: string | undefined
  public tableDisable = false
  public idlookup: any[] | undefined

  public refreshCache: boolean = false

  public currentLineagePathLibTable: string = ''
  public currentLineagePathColumn: string = ''

  public largeDotFileLines: number | null = null
  public largeDotFileLimit: number = 1000 //default is 1000

  public limitDotDepth: boolean = false

  public pendingRenderDownload: string | null = null

  constructor(
    private licenceService: LicenceService,
    private sasService: SasService,
    private route: ActivatedRoute,
    public router: Router,
    private location: Location,
    private eventService: EventService,
    private loggerService: LoggerService,
    public helperService: HelperService
  ) {}

  public showTableSelect() {
    this.tableFlag = !this.tableFlag
  }

  public async tableOnClick(
    tableuri: string,
    libTable: any,
    library: any,
    startupLoad: boolean = false
  ) {
    if (!libTable['columns']) {
      libTable['expanded'] = !libTable['expanded']
      libTable['loadingColumns'] = true
      this.table = tableuri

      this.currentLineagePathLibTable =
        libTable.LIBNAME + '.' + libTable.TABLENAME

      await this.selectTable(tableuri, libTable)

      if (!startupLoad) {
        this.onGenerateGraphTableClick()
      }
    } else {
      libTable['expanded'] = !libTable['expanded']

      if (libTable['expanded'] === true) {
        this.table = tableuri
        if (!startupLoad) {
          this.onGenerateGraphTableClick()
        }
      }
    }

    this.collapseTreeItems(library['tables'], libTable)
  }

  public async selectTable($event: any, libTableTree?: any) {
    this.columnsList = []

    let libTable = { SASControlTable: [{ tableuri: $event }] }

    await this.sasService
      .request('lineage/getmetacols', libTable)
      .then((res: RequestWrapperResponse) => {
        this.columnsList = res.adapterResponse.metacols
        if (this.columnsList && this.columnsList.length > 0) {
          // this.column = this.columnsList[0]['COLURI']

          libTableTree['columns'] = this.columnsList
          libTableTree['expanded'] = true
          libTableTree['loadingColumns'] = false
        }
      })
      .catch((err: any) => err)

    this.setGlobalData()
  }

  public loadMoreLibraries() {
    if (!this.librariesPaging) {
      this.librariesPaging = true

      this.helperService.displayLibraries(this.libraryList, true)

      this.librariesPaging = false
    }
  }

  public collapseTreeItems(tree: any, itemToSkip: any) {
    tree.forEach((item: any) => {
      if (JSON.stringify(item) !== JSON.stringify(itemToSkip)) {
        item['expanded'] = false
      }
    })
  }

  public treeNodeClicked(event: any, item: any, tree: any) {
    if (event.target.title === 'Collapse') {
      this.collapseTreeItems(tree, item)
    }
  }

  public async libraryOnClick(libid: string, library: any) {
    library['inForeground'] = true //When we programatically call this function, we make sure it is visible in the sidebar

    if (!library['tables']) {
      library['loadingTables'] = true

      await this.selectLibrary(libid, library)
    } else {
      library['expanded'] = !library['expanded']
    }

    this.collapseTreeItems(this.libraryList, library)
  }
  public async selectLibrary($event: any, library?: any) {
    this.tablesList = []
    this.columnsList = []

    let libTable = { SASControlTable: [{ liburi: $event }] }
    await this.sasService
      .request('lineage/getmetatables', libTable)
      .then((res: RequestWrapperResponse) => {
        this.tablesList = res.adapterResponse.metatables

        if (this.tablesList && this.tablesList.length > 0) {
          library['tables'] = this.tablesList
          library['expanded'] = true
        }

        this.setGlobalData()
      })
      .catch((err) => err)

    library['loadingTables'] = false
  }

  public columnOnClick(libColumn: any, library: any, libTable: any) {
    this.lib = library.LIBRARYID
    this.table = libTable.TABLEURI
    this.column = libColumn.COLURI

    this.setGlobalData()
    this.onGenerateClick()
  }

  public treeOnFilter(array: any, arrToFilter: string) {
    this.helperService.treeOnFilter(array, arrToFilter)
  }

  public libraryOnFilter() {
    this.helperService.libraryOnFilter(
      this.libraryList,
      this.librariesSearch,
      'LIBRARYNAME'
    )

    globals.lineage.librariesSearch = this.librariesSearch
  }

  public libColumnActive(libColumnUri: string) {
    if (!this.column) {
      return false
    }

    let splitedLibColumnUri = libColumnUri.split('\\')
    let splitedColumnUri = this.column.split('\\')

    return (
      splitedLibColumnUri[splitedLibColumnUri.length - 1] ==
      splitedColumnUri[splitedColumnUri.length - 1]
    )
  }

  public setGlobalData() {
    globals.lineage.libraryList = this.libraryList
    globals.lineage.tablesList = this.tablesList
    globals.lineage.columnsList = this.columnsList

    globals.lineage.lib = this.lib
    globals.lineage.table = this.table
    globals.lineage.column = this.column
  }

  public loadGlobalData() {
    this.libraryList = globals.lineage.libraryList
    this.tablesList = globals.lineage.tablesList
    this.columnsList = globals.lineage.columnsList
    this.librariesSearch = globals.lineage.librariesSearch
  }

  public resubmitWithMaxDepth(maxDepth: number | string) {
    this.cancelRenderingGraph()

    let queryParams = undefined

    if (maxDepth) {
      queryParams = {
        max_depth: maxDepth
      }
    }

    this.router.navigate([], {
      relativeTo: this.route,
      queryParams: queryParams
    })
  }

  ngOnInit(): void {
    globals.viewer.currentSelection = 'view/lineage'
    let reload = this.route.snapshot.params['reload']

    if (reload !== undefined) {
      let url = this.router.url
      if (reload === 'reload') {
        if (!localStorage.getItem('firstLoad')) {
          localStorage['firstLoad'] = true
          setTimeout(function () {
            window.location.reload()
          }, 2000)
        } else {
          localStorage.removeItem('firstLoad')
          this.location.replaceState(url.slice(0, url.lastIndexOf('/')))
        }
      } else {
        this.location.replaceState(url.slice(0, url.lastIndexOf('/')))
      }
    }

    this.initData()
  }

  public async initData() {
    let coluri = this.route.snapshot.params['coluri']
    let direction = this.route.snapshot.params['direction']
    let table_id = this.route.snapshot.params['tableid']

    if (globals.lineage.libraryList) {
      if (globals.lineage.libraryList.length > 0) {
        this.loadGlobalData()
      } else {
        await this.sasService
          .request('public/viewlibs', null)
          .then((res: RequestWrapperResponse) => {
            this.libraryList = res.adapterResponse.saslibs
            this.helperService.displayLibraries(this.libraryList)

            if (this.libraryList) {
              if (this.libraryList.length > 0) {
                this.lib = this.libraryList[0]['LIBRARYID']
              }
            }
            this.setGlobalData()
          })
          .catch((err: any) => err)
      }

      this.route.queryParams.subscribe((queryParams) => {
        this.onRouteChange()
      })

      this.route.params.subscribe((params) => {
        this.onRouteChange()
      })
    }
  }

  debounceTimeout: any
  debounceTime: number = 200 //ms
  public onRouteChange() {
    clearTimeout(this.debounceTimeout)

    this.debounceTimeout = setTimeout(() => {
      const coluri = this.route.snapshot.params['coluri']
      const direction = this.route.snapshot.params['direction']
      const table_id = this.route.snapshot.params['tableid']
      const max_depth = this.route.snapshot.queryParams['max_depth']

      if (coluri && direction) {
        this.forwardLineage = direction === 'FORWARD'
        this.column = coluri
        this.generateGraph(coluri, direction, max_depth)
      }
      if (!this.router.url.includes('column') && table_id && direction) {
        this.forwardLineage = direction === 'FORWARD'
        this.table = table_id
        this.generateGraphTableLevel(table_id, direction, max_depth)
      }
    }, this.debounceTime)
  }

  ngAfterContentInit(): void {}

  public directionText() {
    return this.forwardLineage ? 'FORWARD' : 'REVERSE'
  }

  public onGenerateClick() {
    let directionText = this.directionText()
    let parsedColumnUrl = this.column
      ? this.column.substring(this.column.indexOf('\\') + 1)
      : null

    this.router.navigateByUrl(
      '/view/lineage/column/' + parsedColumnUrl + '/' + directionText
    )
  }

  public onGenerateGraphTableClick() {
    let directionText = this.directionText()
    let table_id = this.table!.includes('\\')
      ? this.table!.split('\\')[1]
      : this.table

    this.router.navigateByUrl('/view/lineage/' + table_id + '/' + directionText)
  }

  public async generateGraphTableLevel(
    table_id: string,
    urlDirection?: string,
    max_depth?: string
  ) {
    let libTable: any = {
      SASControlTable: [
        {
          table_id: table_id,
          direction: urlDirection ? urlDirection : this.directionText()
        }
      ]
    }

    if (libTable.SASControlTable[0].table_id === 'undefined')
      this.eventService.showAbortModal(
        'generateGraphTableLevel()',
        'table_id is undefined.',
        undefined,
        'Frontend error'
      )

    if (max_depth) libTable.SASControlTable[0].max_depth = max_depth

    this.tableFlag = false
    this.switchFlag = true
    this.graphContainer = true
    this.graphLoading = true
    this.vizInput = ''

    return new Promise<void>((resolve, reject) => {
      this.sasService
        .request('lineage/fetchtablelineage', libTable)
        .then(async (res: RequestWrapperResponse) => {
          if (res.adapterResponse.flatdata.length > 0) {
            if (this.licenceService.checkLineageLimit()) {
              this.eventService.showInfoModal(
                'Notice',
                `You have reached daily maximum of lineage diagram renderings. To unlock additional diagrams, contact support@datacontroller.io`
              )
              this.router.navigateByUrl('/view/lineage')
              return
            }
          }

          if (typeof res === 'string') {
            this.vizInput = 'digraph G {SAS Error}'
            this.buildGraph()
            return
          }

          this.lineageTableName =
            res.adapterResponse.info[0].LIBREF +
            '.' +
            res.adapterResponse.info[0].TABLENAME

          let dotArray = res.adapterResponse.finalfinal
          let vizTmp: string = ''

          for (let i = 0; i < dotArray.length; i++) {
            vizTmp += unescape(dotArray[i].LINE) + '\n'
          }

          this.flatdata = res.adapterResponse.flatdata

          if (this.libraryList) {
            let libraryToSelect = this.libraryList.find((library: any) =>
              res.adapterResponse.info[0].LIBURI.toUpperCase().includes(
                library.LIBRARYID.toUpperCase()
              )
            )

            if (libraryToSelect) {
              let tableToSelect

              await this.libraryOnClick(
                libraryToSelect.LIBRARYID,
                libraryToSelect
              )

              if (libraryToSelect['tables']) {
                tableToSelect = libraryToSelect['tables'].find((table: any) =>
                  table.TABLEURI.toUpperCase().includes(
                    res.adapterResponse.info[0].TABLEID.toUpperCase()
                  )
                )

                if (tableToSelect) {
                  this.table = tableToSelect.TABLEURI

                  if (this.table) {
                    const query = this.table.replace('\\', '\\\\')

                    setTimeout(() => {
                      let tablePElement = document.querySelector(
                        `[id='${query}']`
                      )

                      if (tablePElement) {
                        tablePElement.scrollIntoView()
                      }
                    }, 1000)
                  }

                  this.tableOnClick(
                    tableToSelect.TABLEURI,
                    tableToSelect,
                    libraryToSelect,
                    urlDirection !== undefined
                  )
                }
              }

              if (libraryToSelect) {
                libraryToSelect['expanded'] = true
              }

              if (tableToSelect) {
                tableToSelect['expanded'] = true
              }
            }
          }

          this.vizInput = vizTmp ? vizTmp : 'digraph G {No Lineage Available}'
          this.vizInput = this.vizInput
            .replace(/\sds:/g, '\nds:')
            .replace(/\s\n/g, '\n')

          this.idlookup = res.adapterResponse.idlookup

          if (res.adapterResponse.finalfinal.length > this.largeDotFileLimit) {
            this.largeDotFileLines = res.adapterResponse.finalfinal.length
          } else {
            this.buildGraph()
          }

          resolve()
        })
        .catch((err: any) => {
          this.graphLoading = false
          this.graphContainer = false
        })
    })
  }

  public cancelRenderingGraph() {
    this.vizInput = 'digraph G {No Lineage Available}'

    this.largeDotFileLines = null
    this.buildGraph()
  }

  public continueRenderingGraph() {
    this.largeDotFileLines = null

    this.buildGraph(() => {
      if (this.pendingRenderDownload !== null) {
        switch (this.pendingRenderDownload) {
          case 'PNG': {
            this.downloadPNG()

            break
          }
          case 'SVG': {
            this.downloadSVG()

            break
          }
        }

        this.pendingRenderDownload = null
        this.vizInput = 'digraph G {No Lineage Available}'
        this.buildGraph()
      }
    })
  }

  public renderToDownload(type: string) {
    switch (type) {
      case 'PNG': {
        this.pendingRenderDownload = 'PNG'
        this.continueRenderingGraph()

        break
      }
      case 'SVG': {
        this.pendingRenderDownload = 'SVG'
        this.continueRenderingGraph()

        break
      }
    }
  }

  public makeGraphLinkable(idlookup: any) {
    let graphNodes: any = document.querySelectorAll('#graph .node')

    for (let node of graphNodes) {
      let metaid = node.querySelector('title').innerHTML
      let meta = idlookup.find((x: any) => x.METAID === metaid)

      let href: string

      if (meta) {
        if (meta.METATYPE === 'TABLE') {
          href = '/view/data/' + meta.METANAME
        } else {
          href = '/view/metadata/object/' + meta.METAID
        }

        node.classList.add('cursor-pointer')
        node.addEventListener('click', (event: MouseEvent) => {
          this.router.navigateByUrl(href)
        })
      }
    }
  }

  public async generateGraph(
    urlColumn?: String | undefined,
    urlDirection?: string,
    max_depth?: string
  ): Promise<any> {
    let libTable: any = {
      SASControlTable: [
        {
          coluri: urlColumn ? urlColumn : this.column,
          direction: urlDirection ? urlDirection : this.directionText(),
          refresh: this.refreshCache ? 1 : 0
        }
      ]
    }

    if (libTable.SASControlTable[0].coluri === 'undefined')
      this.eventService.showAbortModal(
        'generateGraph()',
        'coluri is undefined.',
        undefined,
        'Frontend error'
      )

    if (max_depth) libTable.SASControlTable[0].max_depth = max_depth

    this.tableFlag = false
    this.switchFlag = true
    this.graphContainer = true
    this.graphLoading = true
    this.vizInput = ''

    return new Promise<void>((resolve, reject) => {
      this.sasService
        .request('lineage/fetchcollineage', libTable)
        .then(async (res: RequestWrapperResponse) => {
          if (res.adapterResponse.flatdata.length > 0) {
            if (this.licenceService.checkLineageLimit()) {
              this.eventService.showInfoModal(
                'Notice',
                `You have reached daily maximum of lineage diagram renderings. To unlock additional diagrams, contact support@datacontroller.io`
              )
              this.router.navigateByUrl('/view/lineage')
              return
            }
          }

          if (typeof res.adapterResponse === 'string') {
            this.vizInput = 'digraph G {SAS Error}'
            this.buildGraph()
            return
          }

          this.lineageTableName =
            res.adapterResponse.info[0].LIBREF +
            '.' +
            res.adapterResponse.info[0].TABNAME
          this.lineageColumnName = res.adapterResponse.info[0].COLNAME

          this.idlookup = res.adapterResponse.idlookup

          let dotArray = res.adapterResponse.fromsas
          let vizTmp: string = ''
          for (let i = 0; i < dotArray.length; i++) {
            vizTmp += unescape(dotArray[i].STRING) + '\n'
          }

          this.vizInput = vizTmp ? vizTmp : 'digraph G {No Lineage Available}'
          this.vizInput = this.vizInput
            .replace(/\sds:/g, '\nds:')
            .replace(/\s\n/g, '\n')

          this.flatdata = res.adapterResponse.flatdata

          if (this.libraryList) {
            let libraryToSelect = this.libraryList.find((library: any) =>
              res.adapterResponse.info[0]?.LIBURI?.toUpperCase()?.includes(
                library?.LIBRARYID?.toUpperCase()
              )
            )

            let tableToSelect: any

            if (libraryToSelect) {
              await this.libraryOnClick(
                libraryToSelect.LIBRARYID,
                libraryToSelect
              )

              if (libraryToSelect['tables']) {
                tableToSelect = libraryToSelect['tables'].find(
                  (table: any) =>
                    table.TABLEURI === res.adapterResponse.info[0].TABURI
                )

                if (tableToSelect) {
                  this.tableOnClick(
                    tableToSelect.TABLEURI,
                    tableToSelect,
                    libraryToSelect,
                    true
                  ).then(() => {
                    let tableUri = tableToSelect.TABLEURI

                    if (tableUri) {
                      const query = tableUri.replace('\\', '\\\\')
                      let tablePElement = document.querySelector(
                        `[id='${query}']`
                      )

                      setTimeout(() => {
                        if (tablePElement) {
                          this.loggerService.log(
                            'libraryToSelect',
                            libraryToSelect
                          )
                          tablePElement.scrollIntoView()
                        }
                      }, 1000)
                    }
                  })
                }
              }

              if (libraryToSelect) {
                libraryToSelect['expanded'] = true
              }

              if (tableToSelect) {
                tableToSelect['expanded'] = true
              }
            }
          }

          if (res.adapterResponse.fromsas.length > this.largeDotFileLimit) {
            this.largeDotFileLines = res.adapterResponse.fromsas.length
          } else {
            this.buildGraph()
          }

          resolve()
        })
        .catch((err: any) => {
          this.graphLoading = false
          this.graphContainer = false
        })
    })
  }

  private getSVGURL() {
    let svg: any = document.getElementById('graph')
    let serializer = new XMLSerializer()
    let svg_blob = new Blob([serializer.serializeToString(svg)], {
      type: 'image/svg+xml'
    })
    return URL.createObjectURL(svg_blob)
  }

  downloadSVG() {
    d3Viz.graphviz('#graph').resetZoom()

    let downloadLink = document.createElement('a')
    downloadLink.href = this.getSVGURL()
    downloadLink.download = this.constructName('svg')
    downloadLink.click()
  }

  async downloadPNG() {
    d3Viz.graphviz('#graph').resetZoom()

    saveSvg.saveSvgAsPng(
      document.querySelector('#graph svg'),
      this.constructName('png')
    )
  }

  downloadCSV() {
    let data = this.flatdata

    const replacer = (key: any, value: any) => (value === null ? '' : value)
    const header = Object.keys(data[0])
    let csv = data.map((row: any) =>
      header
        .map((fieldName) => JSON.stringify(row[fieldName], replacer))
        .join(',')
    )
    csv.unshift(header.join(','))
    let csvArray = csv.join('\r\n')

    var a = document.createElement('a')
    var blob = new Blob([csvArray], { type: 'text/csv' })

    var url = window.URL.createObjectURL(blob)
    a.href = url
    a.download = this.constructName('csv')
    a.click()
    window.URL.revokeObjectURL(url)
  }

  private getDotUrl() {
    let data = this.vizInput
    let dot_blob = new Blob([data], { type: 'text/plain' })
    return window.URL.createObjectURL(dot_blob)
  }

  downloadDot() {
    let downloadLink = document.createElement('a')
    downloadLink.href = this.getDotUrl()
    downloadLink.download = this.constructName('txt')
    downloadLink.click()
  }

  public showSvg() {
    window.open(this.getSVGURL(), '_blank')
  }

  public buildGraph(callback?: any) {
    this.eventService.closeSidebar()

    this.graphLoading = false
    this.graphRendering = true

    setTimeout(() => {
      d3Viz
        .graphviz('#graph')
        .zoom(true)
        .addImage(
          'https://datacontroller.io/wp-content/uploads/2020/01/fc1.png',
          '30px',
          '30px'
        )
        .renderDot(this.vizInput, () => {
          this.graphRendering = false

          if (!!this.idlookup) {
            this.makeGraphLinkable(this.idlookup)
          }

          if (callback) {
            callback()
          }
        })
    }, 100)
  }

  public constructName(extension: string) {
    let libraryName = ''
    let tableName = ''
    let columnName = ''
    let date = moment().format('YYMMDD_HHmm')
    if (this.libraryList && this.libraryList.length) {
      let library: any = this.libraryList.find(
        (e: any) => e.LIBRARYID == this.lib
      )
      if (library) {
        libraryName = `_${library.LIBRARYNAME}`
      }
    }
    if (this.tablesList && this.tablesList.length) {
      let table: any = this.tablesList.find(
        (e: any) => e.TABLEURI == this.table
      )
      if (table) {
        tableName = `_${table.TABLENAME}`
      }
    }
    if (this.columnsList && this.columnsList.length) {
      let column: any = this.columnsList.find(
        (e: any) => e.COLURI == this.column
      )
      if (column) {
        columnName = `_${column.COLNAME}`
      }
    }
    return `${this.directionText()}${libraryName}${tableName}${columnName}_${date}.${extension}`
  }
}
