/*
Copyright (C) 2017  Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import React from 'react'
import { observer } from 'mobx-react'
import styled from 'styled-components'

import notificationStore from '@src/stores/NotificationStore'

import EndpointLogos from '@src/components/modules/EndpointModule/EndpointLogos'
import Button from '@src/components/ui/Button'
import StatusImage from '@src/components/ui/StatusComponents/StatusImage'

import { ThemePalette, ThemeProps } from '@src/components/Theme'
import FileUtils from '@src/utils/FileUtils'
import configLoader from '@src/utils/Config'

import type { FileContent } from '@src/utils/FileUtils'
import type { Endpoint, MultiValidationItem } from '@src/@types/Endpoint'

import { ProviderTypes } from '@src/@types/Providers'
import { Region } from '@src/@types/Region'
import MultipleUploadedEndpoints from './MultipleUploadedEndpoints'

const Wrapper = styled.div<any>`
  display: flex;
  min-height: 0;
  padding: 22px 0 32px 0;
  text-align: center;
`
const Providers = styled.div`
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
`
const Logos = styled.div<any>`
  display: flex;
  flex-wrap: wrap;
  overflow: auto;
  min-height: 0;
  flex-grow: 1;
`
const Upload = styled.div<any>`
  border: 1px dashed ${props => (props.highlight ? ThemePalette.primary : 'white')};
  margin: 0 32px 16px 32px;
  padding: 16px;
`
const UploadMessage = styled.div<any>`
  color: ${ThemePalette.grayscale[3]};
`
const UploadMessageButton = styled.span`
  color: ${ThemePalette.primary};
  cursor: pointer;
`
const FakeFileInput = styled.input`
  position: absolute;
  opacity: 0;
  top: -99999px;
`
const EndpointLogosStyled = styled(EndpointLogos)`
  transform: scale(0.67);
  transition: all ${ThemeProps.animations.swift};
  cursor: pointer;
  &:hover {
    transform: scale(0.7);
  }
`
const LoadingWrapper = styled.div<any>`
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 32px 0;
  flex-grow: 1;
`
const LoadingText = styled.div<any>`
  font-size: 18px;
  margin-top: 32px;
`
type Props = {
  providers: ProviderTypes[],
  regions: Region[]
  onCancelClick: () => void,
  onProviderClick: (provider: ProviderTypes) => void,
  onUploadEndpoint: (endpoint: Endpoint) => void,
  loading: boolean,
  onValidateMultipleEndpoints: (endpoints: Endpoint[]) => void,
  onResizeUpdate?: () => void,
  multiValidating: boolean,
  multiValidation: MultiValidationItem[],
  onRemoveEndpoint: (endpoint: Endpoint) => void,
  onResetValidation: () => void,
}
type State = {
  highlightDropzone: boolean,
  multipleUploadedEndpoints: (Endpoint | string)[],
  invalidRegionsEndpointIds: { id: string, regions: string[] }[],
}
@observer
class ChooseProvider extends React.Component<Props, State> {
  state: State = {
    highlightDropzone: false,
    multipleUploadedEndpoints: [],
    invalidRegionsEndpointIds: [],
  }

  fileInput: HTMLElement | null | undefined

  dragDropListeners: { type: string, listener: (e: any) => any }[] = []

  UNSAFE_componentWillMount() {
    setTimeout(() => { this.addDragAndDrop() }, 1000)
  }

  componentDidUpdate(_: Props, prevState: State) {
    if (prevState.multipleUploadedEndpoints.length !== this.state.multipleUploadedEndpoints.length
      && this.props.onResizeUpdate) {
      this.props.onResizeUpdate()
    }
  }

  componentWillUnmount() {
    this.removeDragDrop()
  }

  addDragAndDrop() {
    this.dragDropListeners = [{
      type: 'dragenter',
      listener: e => {
        this.setState({ highlightDropzone: true })
        e.dataTransfer.dropEffect = 'copy'
        e.preventDefault()
      },
    }, {
      type: 'dragover',
      listener: e => {
        e.dataTransfer.dropEffect = 'copy'
        e.preventDefault()
      },
    }, {
      type: 'dragleave',
      listener: e => {
        if (!e.clientX && !e.clientY) {
          this.setState({ highlightDropzone: false })
        }
      },
    }, {
      type: 'drop',
      listener: async e => {
        e.preventDefault()
        this.setState({ highlightDropzone: false })
        const filesContents = await FileUtils.readContentFromFileList(e.dataTransfer.files)
        if (filesContents.length === 1) {
          this.processOneFileContent(filesContents[0].content)
        } else {
          this.processMultipleFilesContents(filesContents)
        }
      },
    }]

    this.dragDropListeners.forEach(l => {
      window.addEventListener(l.type, l.listener)
    })
  }

  removeDragDrop() {
    this.dragDropListeners.forEach(l => {
      window.removeEventListener(l.type, l.listener)
    })
    this.dragDropListeners = []
  }

  parseEndpoint(content: string, skipAlert?: boolean): { endpoint: Endpoint, unidentRegions: string[] } {
    const endpoint: Endpoint = JSON.parse(content)
    if (!endpoint.name || !endpoint.type || !this.props.providers.find(p => p === endpoint.type)) {
      throw new Error()
    }
    delete (endpoint as any).id
    const unidentRegions: string[] = []

    if (endpoint.mapped_regions?.length) {
      endpoint.mapped_regions = endpoint.mapped_regions.map(nameId => {
        const region = this.props.regions.find(r => r.id === nameId || r.name === nameId)
        if (region) {
          return region.id
        }
        unidentRegions.push(nameId)
        return null
      }).filter((item: string | null): item is string => Boolean(item))
      if (unidentRegions.length && !skipAlert) {
        notificationStore.alert(`${unidentRegions.length} Coriolis Region${unidentRegions.length > 1 ? 's' : ''} couldn't be mapped`, 'warning')
      }
    }
    return { endpoint, unidentRegions }
  }

  processOneFileContent(content: string) {
    this.props.onResetValidation()
    try {
      const { endpoint } = this.parseEndpoint(content)
      this.chooseEndpoint(endpoint)
    } catch (err) {
      notificationStore.alert('Invalid .endpoint file', 'error')
    }
  }

  processMultipleFilesContents(filesContents: FileContent[]) {
    this.props.onResetValidation()
    const uniqueNames: { [prop: string]: number } = {}
    const invalidRegionsEndpointIds: { id: string, regions: string[] }[] = []

    const endpoints = filesContents.map(fileContent => {
      try {
        const { endpoint, unidentRegions } = this.parseEndpoint(fileContent.content, true)
        const key = `${endpoint.type}${endpoint.name}`
        if (uniqueNames[key] === undefined) {
          uniqueNames[key] = 0
        } else {
          uniqueNames[key] += 1
          endpoint.name = `${endpoint.name} (${uniqueNames[key]})`
        }
        if (unidentRegions.length) {
          invalidRegionsEndpointIds.push({ id: `${endpoint.type}${endpoint.name}`, regions: unidentRegions })
        }
        return endpoint
      } catch (err) {
        return fileContent.name
      }
    })

    const sortPriority = configLoader.config.providerSortPriority
    endpoints.sort((a, b) => {
      if (typeof a === 'string' && typeof b === 'string') {
        return a.localeCompare(b)
      }
      if (typeof a === 'string') {
        return 1
      }
      if (typeof b === 'string') {
        return -1
      }
      if (sortPriority[a.type] && sortPriority[b.type]) {
        return (sortPriority[a.type] - sortPriority[b.type]) || a.type.localeCompare(b.type)
      }
      if (sortPriority[a.type]) {
        return -1
      }
      if (sortPriority[b.type]) {
        return 1
      }
      return a.type.localeCompare(b.type)
    })

    this.setState({
      multipleUploadedEndpoints: endpoints,
      invalidRegionsEndpointIds,
    })
  }

  chooseEndpoint(endpoint: Endpoint) {
    this.props.onUploadEndpoint(endpoint)
  }

  async handleFileUpload(files: FileList | null) {
    const filesContents = await FileUtils.readContentFromFileList(files)
    if (filesContents.length === 1) {
      this.processOneFileContent(filesContents[0].content)
    } else {
      this.processMultipleFilesContents(filesContents)
    }
  }

  handleRemoveUploadedEndpoint(endpoint: Endpoint | string, isAdded: boolean) {
    this.setState(prevState => {
      const multipleUploadedEndpoints = prevState.multipleUploadedEndpoints.filter(e => {
        if (typeof e === 'string' && typeof endpoint === 'string') {
          return e !== endpoint
        }
        if (typeof e !== 'string' && typeof endpoint !== 'string') {
          return e.name !== endpoint.name || e.type !== endpoint.type
        }
        return true
      })
      if (isAdded && typeof endpoint !== 'string') {
        this.props.onRemoveEndpoint(endpoint)
      }
      return { multipleUploadedEndpoints }
    })
  }

  handleRegionsChange(endpoint: Endpoint, newRegions: string[]) {
    this.setState(prevState => ({
      multipleUploadedEndpoints: prevState.multipleUploadedEndpoints.map(stateEndpoint => {
        if (typeof stateEndpoint !== 'string' && `${stateEndpoint.type}${stateEndpoint.name}` === `${endpoint.type}${endpoint.name}`) {
          return {
            ...stateEndpoint,
            mapped_regions: newRegions,
          }
        }
        return stateEndpoint
      }),
    }))
  }

  renderMultipleUploadedEndpoints() {
    return (
      <MultipleUploadedEndpoints
        endpoints={this.state.multipleUploadedEndpoints}
        onBackClick={() => { this.setState({ multipleUploadedEndpoints: [] }) }}
        onRemove={(e, isAdded) => { this.handleRemoveUploadedEndpoint(e, isAdded) }}
        validating={this.props.multiValidating}
        multiValidation={this.props.multiValidation}
        invalidRegionsEndpointIds={this.state.invalidRegionsEndpointIds}
        regions={this.props.regions}
        onRegionsChange={(endpoint, newRegions) => { this.handleRegionsChange(endpoint, newRegions) }}
        onValidateClick={() => {
          this.props.onValidateMultipleEndpoints(this.state.multipleUploadedEndpoints.filter(e => typeof e !== 'string') as Endpoint[])
        }}
        onDone={this.props.onCancelClick}
      />
    )
  }

  renderLoading() {
    if (!this.props.loading) {
      return null
    }

    return (
      <LoadingWrapper>
        <StatusImage loading />
        <LoadingText>Loading providers ...</LoadingText>
      </LoadingWrapper>
    )
  }

  renderProviders() {
    if (this.props.loading) {
      return null
    }

    const UploadButton = (
      <UploadMessageButton
        onClick={() => { if (this.fileInput) this.fileInput.click() }}
      >upload
      </UploadMessageButton>
    )

    return (
      <Providers>
        <Logos>
          {this.props.providers.map(k => (
            <EndpointLogosStyled
              height={128}
              key={k}
              endpoint={k}
              data-test-id={`cProvider-endpointLogo-${k}`}
              onClick={() => { this.props.onProviderClick(k) }}
            />
          ))}
        </Logos>
        <Upload highlight={this.state.highlightDropzone}>
          <UploadMessage>
            You can
            &nbsp;{UploadButton}&nbsp;
            or drop multiple .endpoint and zipped .endpoint files.
          </UploadMessage>
        </Upload>
        <FakeFileInput
          type="file"
          ref={r => { this.fileInput = r }}
          accept=".endpoint,.zip"
          multiple
          onChange={e => { this.handleFileUpload(e.target.files) }}
        />
        <Button secondary onClick={this.props.onCancelClick} data-test-id="cProvider-cancelButton">Cancel</Button>
      </Providers>
    )
  }

  render() {
    return (
      <Wrapper>
        {this.state.multipleUploadedEndpoints.length === 0 ? this.renderProviders() : null}
        {this.renderLoading()}
        {this.state.multipleUploadedEndpoints.length > 0
          ? this.renderMultipleUploadedEndpoints() : null}
      </Wrapper>
    )
  }
}

export default ChooseProvider
