import React, { ReactElement, useEffect, useState } from 'react'
import web3 from 'web3'
import Time from '../../../atoms/Time'
import { Link } from 'gatsby'
import { DDO, Logger, Service, Provider } from '@oceanprotocol/lib'
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
import Dotdotdot from 'react-dotdotdot'
import Table from '../../../atoms/Table'
import Button from '../../../atoms/Button'
import { useOcean } from '../../../../providers/Ocean'
import { gql, useQuery } from 'urql'
import { ComputeOrders } from '../../../../@types/apollo/ComputeOrders'
import { useWeb3 } from '../../../../providers/Web3'
import {
  queryMetadata,
  transformChainIdsListToQuery
} from '../../../../utils/aquarius'
import axios, { CancelToken } from 'axios'
import Details from './Details'
import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
import { ReactComponent as Refresh } from '../../../../images/refresh.svg'
import styles from './index.module.css'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import { useUserPreferences } from '../../../../providers/UserPreferences'

const getComputeOrders = gql`
  query ComputeOrders($user: String!) {
    tokenOrders(
      orderBy: timestamp
      orderDirection: desc
      where: { payer: $user }
    ) {
      id
      serviceId
      datatokenId {
        address
      }
      tx
      timestamp
    }
  }
`

export function Status({ children }: { children: string }): ReactElement {
  return <div className={styles.status}>{children}</div>
}

const columns = [
  {
    name: 'Data Set',
    selector: function getAssetRow(row: ComputeJobMetaData) {
      return (
        <Dotdotdot clamp={2}>
          <Link to={`/asset/${row.inputDID[0]}`}>{row.assetName}</Link>
        </Dotdotdot>
      )
    }
  },
  {
    name: 'Created',
    selector: function getTimeRow(row: ComputeJobMetaData) {
      return <Time date={row.dateCreated} isUnix relative />
    }
  },
  {
    name: 'Finished',
    selector: function getTimeRow(row: ComputeJobMetaData) {
      return row.dateFinished ? (
        <Time date={row.dateFinished} isUnix relative />
      ) : (
        ''
      )
    }
  },
  {
    name: 'Status',
    selector: function getStatus(row: ComputeJobMetaData) {
      return <Status>{row.statusText}</Status>
    }
  },
  {
    name: 'Actions',
    selector: function getActions(row: ComputeJobMetaData) {
      return <Details job={row} />
    }
  }
]

async function getAssetMetadata(
  queryDtList: string,
  cancelToken: CancelToken,
  chainIds: number[]
): Promise<DDO[]> {
  const queryDid = {
    page: 1,
    offset: 100,
    query: {
      query_string: {
        query: `(${queryDtList}) (${transformChainIdsListToQuery(
          chainIds
        )}) AND (${transformChainIdsListToQuery(
          chainIds
        )}) AND service.attributes.main.type:dataset AND service.type:compute`,
        fields: ['dataToken']
      }
    }
  }

  const result = await queryMetadata(queryDid, cancelToken)

  return result.results
}

export default function ComputeJobs(): ReactElement {
  const { ocean, account, config } = useOcean()
  const { accountId } = useWeb3()
  const [isLoading, setIsLoading] = useState(true)
  const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
  const { chainIds } = useUserPreferences()
  const [result] = useQuery<ComputeOrders>({
    query: getComputeOrders,
    variables: {
      user: accountId?.toLowerCase()
    }
  })
  const { data } = result

  async function getJobs() {
    if (!accountId) return
    setIsLoading(true)

    // await refetch()
    const dtList = []
    const computeJobs: ComputeJobMetaData[] = []
    for (let i = 0; i < data.tokenOrders.length; i++) {
      dtList.push(data.tokenOrders[i].datatokenId.address)
    }
    const queryDtList = JSON.stringify(dtList)
      .replace(/,/g, ' ')
      .replace(/"/g, '')
      .replace(/(\[|\])/g, '')

    try {
      const source = axios.CancelToken.source()
      const assets = await getAssetMetadata(queryDtList, source.token, chainIds)
      const providers: Provider[] = []
      const serviceEndpoints: string[] = []
      for (let i = 0; i < data.tokenOrders.length; i++) {
        try {
          const did = web3.utils
            .toChecksumAddress(data.tokenOrders[i].datatokenId.address)
            .replace('0x', 'did:op:')

          const ddo = assets.filter((x) => x.id === did)[0]

          if (!ddo) continue

          const service = ddo.service.filter(
            (x: Service) => x.index === data.tokenOrders[i].serviceId
          )[0]

          if (!service || service.type !== 'compute') continue
          const { serviceEndpoint } = service

          const wasProviderQueried =
            serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0

          if (wasProviderQueried) continue
          serviceEndpoints.push(serviceEndpoint)
        } catch (err) {
          Logger.error(err)
        }
      }

      try {
        for (let i = 0; i < serviceEndpoints.length; i++) {
          const instanceConfig = {
            config,
            web3: config.web3Provider,
            logger: Logger,
            ocean: ocean
          }
          const provider = await Provider.getInstance(instanceConfig)
          await provider.setBaseUrl(serviceEndpoints[i])
          const hasSameCompute =
            providers.filter(
              (x) => x.computeAddress === provider.computeAddress
            ).length > 0
          if (!hasSameCompute) providers.push(provider)
        }
      } catch (err) {
        Logger.error(err)
      }
      for (let i = 0; i < providers.length; i++) {
        try {
          const providerComputeJobs = (await providers[i].computeStatus(
            '',
            account,
            undefined,
            undefined,
            false
          )) as ComputeJob[]

          // means the provider uri is not good, so we ignore it and move on
          if (!providerComputeJobs) continue
          providerComputeJobs.sort((a, b) => {
            if (a.dateCreated > b.dateCreated) {
              return -1
            }
            if (a.dateCreated < b.dateCreated) {
              return 1
            }
            return 0
          })

          for (let j = 0; j < providerComputeJobs.length; j++) {
            const job = providerComputeJobs[j]
            const did = job.inputDID[0]
            const ddo = assets.filter((x) => x.id === did)[0]

            if (!ddo) continue
            const serviceMetadata = ddo.service.filter(
              (x: Service) => x.type === 'metadata'
            )[0]

            const compJob: ComputeJobMetaData = {
              ...job,
              assetName: serviceMetadata.attributes.main.name,
              assetDtSymbol: ddo.dataTokenInfo.symbol
            }
            computeJobs.push(compJob)
          }
        } catch (err) {
          Logger.error(err)
        }
      }
      setJobs(computeJobs)
    } catch (error) {
      Logger.log(error.message)
    } finally {
      setIsLoading(false)
    }
    return true
  }

  useEffect(() => {
    if (data === undefined || !chainIds) {
      setIsLoading(false)
      return
    }
    getJobs()
  }, [ocean, account, data, chainIds])

  return (
    <>
      {jobs.length > 0 && (
        <Button
          style="text"
          size="small"
          title="Refresh compute jobs"
          onClick={() => getJobs()}
          disabled={isLoading}
          className={styles.refresh}
        >
          <Refresh />
          Refresh
        </Button>
      )}
      <Table
        columns={columns}
        data={jobs}
        isLoading={isLoading}
        defaultSortField="row.dateCreated"
        defaultSortAsc={false}
      />
    </>
  )
}
