import axios, { AxiosInstance } from 'axios'
import { getAppConfig, getControledMihomoConfig } from '../config'
import templateIcon from '../../../resources/iconTemplate@2x.png?asset'
import { mainWindow } from '..'
import WebSocket from 'ws'
import { tray } from '../resolve/tray'
import { calcTraffic } from '../utils/calc'
import { getRuntimeConfig } from './factory'
import { nativeImage } from 'electron'
import svg2img from 'svg2img'

const icon = nativeImage.createFromPath(templateIcon)
icon.setTemplateImage(true)
const base64 = icon.toPNG().toString('base64')
let hasShowTraffic = false
let axiosIns: AxiosInstance = null!
let mihomoTrafficWs: WebSocket | null = null
let trafficRetry = 10
let mihomoMemoryWs: WebSocket | null = null
let memoryRetry = 10
let mihomoLogsWs: WebSocket | null = null
let logsRetry = 10
let mihomoConnectionsWs: WebSocket | null = null
let connectionsRetry = 10

export const getAxios = async (force: boolean = false): Promise<AxiosInstance> => {
  if (axiosIns && !force) return axiosIns
  const controledMihomoConfig = await getControledMihomoConfig()
  let server = controledMihomoConfig['external-controller']
  const secret = controledMihomoConfig.secret ?? ''
  if (server?.startsWith(':')) server = `127.0.0.1${server}`

  axiosIns = axios.create({
    baseURL: `http://${server}`,
    proxy: false,
    headers: secret ? { Authorization: `Bearer ${secret}` } : {},
    timeout: 15000
  })

  axiosIns.interceptors.response.use(
    (response) => {
      return response.data
    },
    (error) => {
      if (error.response && error.response.data) {
        return Promise.reject(error.response.data)
      }
      return Promise.reject(error)
    }
  )
  return axiosIns
}

export async function mihomoVersion(): Promise<IMihomoVersion> {
  const instance = await getAxios()
  return await instance.get('/version')
}

export const patchMihomoConfig = async (patch: Partial<IMihomoConfig>): Promise<void> => {
  const instance = await getAxios()
  return await instance.patch('/configs', patch)
}

export const mihomoCloseConnection = async (id: string): Promise<void> => {
  const instance = await getAxios()
  return await instance.delete(`/connections/${encodeURIComponent(id)}`)
}

export const mihomoCloseAllConnections = async (): Promise<void> => {
  const instance = await getAxios()
  return await instance.delete('/connections')
}

export const mihomoRules = async (): Promise<IMihomoRulesInfo> => {
  const instance = await getAxios()
  return await instance.get('/rules')
}

export const mihomoProxies = async (): Promise<IMihomoProxies> => {
  const instance = await getAxios()

  return await instance.get('/proxies')
}

export const mihomoGroups = async (): Promise<IMihomoMixedGroup[]> => {
  const proxies = await mihomoProxies()
  const runtime = await getRuntimeConfig()
  const groups: IMihomoMixedGroup[] = []
  runtime?.['proxy-groups']?.forEach((group: { name: string; url?: string }) => {
    group = Object.assign(group, group['<<'])
    const { name, url } = group
    if (proxies.proxies[name] && 'all' in proxies.proxies[name] && !proxies.proxies[name].hidden) {
      const newGroup = proxies.proxies[name]
      newGroup.testUrl = url
      const newAll = newGroup.all.map((name) => proxies.proxies[name])
      groups.push({ ...newGroup, all: newAll })
    }
  })
  if (!groups.find((group) => group.name === 'GLOBAL')) {
    const newGlobal = proxies.proxies['GLOBAL'] as IMihomoGroup
    const newAll = newGlobal.all.map((name) => proxies.proxies[name])
    groups.push({ ...newGlobal, all: newAll })
  }
  return groups
}

export const mihomoProxyProviders = async (): Promise<IMihomoProxyProviders> => {
  const instance = await getAxios()
  return await instance.get('/providers/proxies')
}

export const mihomoUpdateProxyProviders = async (name: string): Promise<void> => {
  const instance = await getAxios()
  return await instance.put(`/providers/proxies/${encodeURIComponent(name)}`)
}

export const mihomoRuleProviders = async (): Promise<IMihomoRuleProviders> => {
  const instance = await getAxios()
  return await instance.get('/providers/rules')
}

export const mihomoUpdateRuleProviders = async (name: string): Promise<void> => {
  const instance = await getAxios()
  return await instance.put(`/providers/rules/${encodeURIComponent(name)}`)
}

export const mihomoChangeProxy = async (group: string, proxy: string): Promise<IMihomoProxy> => {
  const instance = await getAxios()
  return await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
}

export const mihomoUpgradeGeo = async (): Promise<void> => {
  const instance = await getAxios()
  return await instance.post('/configs/geo')
}

export const mihomoProxyDelay = async (proxy: string, url?: string): Promise<IMihomoDelay> => {
  const appConfig = await getAppConfig()
  const { delayTestUrl, delayTestTimeout } = appConfig
  const instance = await getAxios()
  return await instance.get(`/proxies/${encodeURIComponent(proxy)}/delay`, {
    params: {
      url: url || delayTestUrl || 'https://www.gstatic.com/generate_204',
      timeout: delayTestTimeout || 5000
    }
  })
}

export const mihomoGroupDelay = async (group: string, url?: string): Promise<IMihomoGroupDelay> => {
  const appConfig = await getAppConfig()
  const { delayTestUrl, delayTestTimeout } = appConfig
  const instance = await getAxios()
  return await instance.get(`/group/${encodeURIComponent(group)}/delay`, {
    params: {
      url: url || delayTestUrl || 'https://www.gstatic.com/generate_204',
      timeout: delayTestTimeout || 5000
    }
  })
}

export const mihomoUpgrade = async (): Promise<void> => {
  const instance = await getAxios()
  return await instance.post('/upgrade')
}

export const startMihomoTraffic = async (): Promise<void> => {
  await mihomoTraffic()
}

export const stopMihomoTraffic = (): void => {
  if (mihomoTrafficWs) {
    mihomoTrafficWs.removeAllListeners()
    if (mihomoTrafficWs.readyState === WebSocket.OPEN) {
      mihomoTrafficWs.close()
    }
    mihomoTrafficWs = null
  }
}

const mihomoTraffic = async (): Promise<void> => {
  const { showTraffic = true } = await getAppConfig()
  const controledMihomoConfig = await getControledMihomoConfig()
  let server = controledMihomoConfig['external-controller']
  const secret = controledMihomoConfig.secret ?? ''
  if (server?.startsWith(':')) server = `127.0.0.1${server}`
  stopMihomoTraffic()

  mihomoTrafficWs = new WebSocket(`ws://${server}/traffic?token=${encodeURIComponent(secret)}`)

  mihomoTrafficWs.onmessage = (e): void => {
    const data = e.data as string
    const json = JSON.parse(data) as IMihomoTrafficInfo
    if (process.platform === 'darwin') {
      if (showTraffic) {
        const svgContent = `
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156 36">
        <image height='36' width='36' href='data:image/png;base64,${base64}'/>
        <text x='40' y='15' font-size='18' font-family="PingFang SC" font-weight='bold' text-anchor='start'>↑</text>
        <text x='40' y='34' font-size='18' font-family="PingFang SC" font-weight='bold' text-anchor='start'>↓</text>
        <text x='156' y='15' font-size='18' font-family="PingFang SC" font-weight='bold' text-anchor='end'>${calcTraffic(json.up)}/s</text>
        <text x='156' y='34' font-size='18' font-family="PingFang SC" font-weight='bold' text-anchor='end'>${calcTraffic(json.down)}/s</text>
      </svg>`
        svg2img(svgContent, {}, (error, buffer) => {
          if (error) return
          const image = nativeImage.createFromBuffer(buffer).resize({ height: 16 })
          image.setTemplateImage(true)
          tray?.setImage(image)
        })
        hasShowTraffic = true
      } else {
        if (hasShowTraffic) {
          hasShowTraffic = false
          icon.resize({ height: 16 })
          tray?.setImage(icon)
        }
      }
    }
    if (process.platform !== 'linux') {
      tray?.setToolTip(
        '↑' +
          `${calcTraffic(json.up)}/s`.padStart(9) +
          '\n↓' +
          `${calcTraffic(json.down)}/s`.padStart(9)
      )
    }

    trafficRetry = 10
    mainWindow?.webContents.send('mihomoTraffic', json)
  }

  mihomoTrafficWs.onclose = (): void => {
    if (trafficRetry) {
      trafficRetry--
      mihomoTraffic()
    }
  }

  mihomoTrafficWs.onerror = (): void => {
    if (mihomoTrafficWs) {
      mihomoTrafficWs.close()
      mihomoTrafficWs = null
    }
  }
}

export const startMihomoMemory = async (): Promise<void> => {
  await mihomoMemory()
}

export const stopMihomoMemory = (): void => {
  if (mihomoMemoryWs) {
    mihomoMemoryWs.removeAllListeners()
    if (mihomoMemoryWs.readyState === WebSocket.OPEN) {
      mihomoMemoryWs.close()
    }
    mihomoMemoryWs = null
  }
}

const mihomoMemory = async (): Promise<void> => {
  const controledMihomoConfig = await getControledMihomoConfig()
  let server = controledMihomoConfig['external-controller']
  const secret = controledMihomoConfig.secret ?? ''
  if (server?.startsWith(':')) server = `127.0.0.1${server}`
  stopMihomoMemory()

  mihomoMemoryWs = new WebSocket(`ws://${server}/memory?token=${encodeURIComponent(secret)}`)

  mihomoMemoryWs.onmessage = (e): void => {
    const data = e.data as string
    memoryRetry = 10
    mainWindow?.webContents.send('mihomoMemory', JSON.parse(data) as IMihomoMemoryInfo)
  }

  mihomoMemoryWs.onclose = (): void => {
    if (memoryRetry) {
      memoryRetry--
      mihomoMemory()
    }
  }

  mihomoMemoryWs.onerror = (): void => {
    if (mihomoMemoryWs) {
      mihomoMemoryWs.close()
      mihomoMemoryWs = null
    }
  }
}

export const startMihomoLogs = async (): Promise<void> => {
  await mihomoLogs()
}

export const stopMihomoLogs = (): void => {
  if (mihomoLogsWs) {
    mihomoLogsWs.removeAllListeners()
    if (mihomoLogsWs.readyState === WebSocket.OPEN) {
      mihomoLogsWs.close()
    }
    mihomoLogsWs = null
  }
}

const mihomoLogs = async (): Promise<void> => {
  const controledMihomoConfig = await getControledMihomoConfig()
  const { secret = '', 'log-level': level = 'info' } = controledMihomoConfig
  let { 'external-controller': server } = controledMihomoConfig
  if (server?.startsWith(':')) server = `127.0.0.1${server}`
  stopMihomoLogs()

  mihomoLogsWs = new WebSocket(
    `ws://${server}/logs?token=${encodeURIComponent(secret)}&level=${level}`
  )

  mihomoLogsWs.onmessage = (e): void => {
    const data = e.data as string
    logsRetry = 10
    mainWindow?.webContents.send('mihomoLogs', JSON.parse(data) as IMihomoLogInfo)
  }

  mihomoLogsWs.onclose = (): void => {
    if (logsRetry) {
      logsRetry--
      mihomoLogs()
    }
  }

  mihomoLogsWs.onerror = (): void => {
    if (mihomoLogsWs) {
      mihomoLogsWs.close()
      mihomoLogsWs = null
    }
  }
}

export const startMihomoConnections = async (): Promise<void> => {
  await mihomoConnections()
}

export const stopMihomoConnections = (): void => {
  if (mihomoConnectionsWs) {
    mihomoConnectionsWs.removeAllListeners()
    if (mihomoConnectionsWs.readyState === WebSocket.OPEN) {
      mihomoConnectionsWs.close()
    }
    mihomoConnectionsWs = null
  }
}

const mihomoConnections = async (): Promise<void> => {
  const controledMihomoConfig = await getControledMihomoConfig()
  let server = controledMihomoConfig['external-controller']
  const secret = controledMihomoConfig.secret ?? ''
  if (server?.startsWith(':')) server = `127.0.0.1${server}`
  stopMihomoConnections()

  mihomoConnectionsWs = new WebSocket(
    `ws://${server}/connections?token=${encodeURIComponent(secret)}`
  )

  mihomoConnectionsWs.onmessage = (e): void => {
    const data = e.data as string
    connectionsRetry = 10
    mainWindow?.webContents.send('mihomoConnections', JSON.parse(data) as IMihomoConnectionsInfo)
  }

  mihomoConnectionsWs.onclose = (): void => {
    if (connectionsRetry) {
      connectionsRetry--
      mihomoConnections()
    }
  }

  mihomoConnectionsWs.onerror = (): void => {
    if (mihomoConnectionsWs) {
      mihomoConnectionsWs.close()
      mihomoConnectionsWs = null
    }
  }
}

export const pauseWebsockets = () => {
  const recoverList: (() => void)[] = []
  // Traffic 始终开启
  stopMihomoTraffic()
  recoverList.push(startMihomoTraffic)
  if (mihomoMemoryWs) {
    stopMihomoMemory()
    recoverList.push(startMihomoMemory)
  }
  if (mihomoLogsWs) {
    stopMihomoLogs()
    recoverList.push(startMihomoLogs)
  }
  if (mihomoConnectionsWs) {
    stopMihomoConnections()
    recoverList.push(startMihomoConnections)
  }
  return (): void => {
    recoverList.forEach((recover) => recover())
  }
}
