import { computed, reactive, ref } from 'vue'
import { defineStore } from 'pinia'
import { io } from 'socket.io-client'
import axios from 'axios'
import { useErrorStore } from '@/stores/error'

import api from '@/services/HttpService'

import { useLoader } from './loader'

type Path = string

export interface StorageFolder {
  pathname: string
  name: string
  folderSize?: string
  createdAt: string
  createdBy: string
}

export interface StorageFile {
  pathname: string
  path: string
  fileName: string
  fileSize: string
  fileType: string
  title: string
  description: string
  category: string[]
  keyword: string[]
  updatedAt: string
  updatedBy: string
  createdAt: string
  createdBy: string
}

export interface Structure extends StorageFolder {
  folder: Structure[]
  file: StorageFile[]
}

type Tree = Structure[]

function constructUrl(path: string | string[], append = true) {
  const arr = Array.isArray(path) ? path : path.split('/').filter(Boolean)
  const url =
    import.meta.env.VITE_API_ENDPOINT +
    arr.reduce<string>((a, v, i) => {
      switch (String(i)) {
        case '0':
          return `cabinet/${v}`
        case '1':
          return `${a}/drawer/${v}`
        case '2':
          return `${a}/folder/${v}`
        case '3':
          return `${a}/subfolder/${v}`
        default:
          return a
      }
    }, '')

  return append
    ? url + ['cabinet', '/drawer', '/folder', '/subfolder'][arr.length]
    : url
}

function consistantPath(path: string | string[]) {
  return Array.isArray(path)
    ? path.join('/') + '/'
    : path.split('/').filter(Boolean).join('/') + '/'
}

const useStorage = defineStore('storageStore', () => {
  const error = useErrorStore()
  const loader = useLoader()
  const init = ref<boolean>(false)
  const folder = ref<Record<Path, StorageFolder[]>>({})
  const file = ref<Record<Path, StorageFile[]>>({})
  const tree = computed(() => {
    let structure: Tree = []

    // parse list of folder and list of file into tree
    Object.entries(folder.value).forEach(([key, value]) => {
      const arr = key.split('/').filter(Boolean)

      // Once run then it is init
      if (!init.value) init.value = true

      if (arr.length === 0) {
        structure = value.map((v) => ({
          pathname: v.pathname,
          name: v.name,
          createdAt: v.createdAt,
          createdBy: v.createdBy,
          folder: [],
          file: [],
        }))
      } else {
        let current: Structure | undefined

        // traverse into tree
        arr.forEach((v, i) => {
          current =
            i === 0
              ? structure.find((x) => x.name === v)
              : current?.folder.find((x) => x.name === v)
        })

        // set data in tree (object is references to the same object)
        if (current) {
          current.folder = value.map((v) => ({
            pathname: v.pathname,
            name: v.name,
            createdAt: v.createdAt,
            createdBy: v.createdBy,
            folder: [],
            file: [],
          }))
          current.file = file.value[key] ?? []
        }
      }
    })

    return structure
  })
  const currentInfo = reactive<{
    path: string
    dept: number
  }>({
    path: '',
    dept: 0,
  })

  if (!init.value) goto(sessionStorage.getItem('path') || '')

  async function getStorage(path: string = '') {
    const arr = path.split('/').filter(Boolean)

    if (arr.length >= 4) return // this system does not have more than 4 level

    const res = await api.post<(typeof folder.value)[string]>(
      `${import.meta.env.VITE_API_ENDPOINT}storage/list`,
      {
        operation: 'folder',
        path: arr,
      },
    )
    if (res.status === 200 && res.data && Array.isArray(res.data))
      folder.value[consistantPath(path)] = res.data.sort((a, b) =>
        a.pathname.localeCompare(b.pathname),
      )
  }

  async function getStorageFile(path: string = '') {
    const arr = path.split('/').filter(Boolean)

    if (arr.length < 3) return // file in this system only lives in level 3 and 4

    const res = await api.post<(typeof file.value)[string]>(
      `${import.meta.env.VITE_API_ENDPOINT}storage/list`,
      {
        operation: 'file',
        path: arr,
      },
    )
    if (res.status === 200 && res.data && Array.isArray(res.data))
      file.value[consistantPath(path)] = res.data.sort((a, b) =>
        a.pathname.localeCompare(b.pathname),
      )
  }

  async function goto(path: string = '', force = false) {
    loader.show()
    const arr = path.split('/').filter(Boolean)

    // get all parent to the root structure
    // this will also triggher init structure as it get root structure
    for (let i = 0; i < arr.length; i++) {
      const current = consistantPath(arr.slice(0, i - arr.length))
      if (!folder.value[current] || force) await getStorage(current)
    }

    path = consistantPath(path)

    // only get this path once, after that will get from socket.io-client instead
    if (!folder.value[path] || force) await getStorage(path)
    if (!file.value[path] || force) await getStorageFile(path)

    currentInfo.path = path
    currentInfo.dept = path.split('/').filter(Boolean).length
    sessionStorage.setItem('path', path)
    loader.hide()
  }

  async function gotoParent() {
    const arr = currentInfo.path.split('/').filter(Boolean)
    await goto(consistantPath(arr.slice(0, -1)))
  }

  // socket.io zone
  const socket = io(import.meta.env.VITE_API_HOST)

  socket.on('connect', () => console.info('Socket.io connected.'))
  socket.on('disconnect', () => console.info('Socket.io disconnected.'))
  socket.on(
    'FolderCreate',
    (data: {
      pathname: string
      name: string
      createdAt: string
      createdBy: string
    }) => {
      const arr = data.pathname.split('/').filter(Boolean)
      const path = consistantPath(arr.slice(0, -1))

      if (folder.value[path]) {
        const idx = folder.value[path].findIndex(
          (v) => v.pathname === data.pathname,
        )

        if (idx === -1) {
          folder.value[path].push({
            pathname: data.pathname,
            name: data.name,
            createdAt: data.createdAt,
            createdBy: data.createdBy,
          })
        }
        folder.value[path].sort((a, b) => a.pathname.localeCompare(b.pathname))
      }
    },
  )
  // NOTE:
  // API planned to make new endpoint that can move and rename in one go.
  // Need to change if api handle move and rename file instead of just edit.
  socket.on('FolderMove', (data: { from: string; to: string }) => {
    const src = data.from.split('/').filter(Boolean)
    const dst = data.to.split('/').filter(Boolean)
    const path = consistantPath(src.slice(0, -1))

    if (folder.value[path]) {
      const val = folder.value[path].find((v) => v.pathname === data.from)

      if (val) {
        val.pathname = data.to
        val.name = dst[dst.length - 1]
      }
    }

    const regex = new RegExp(`^${data.from}`)

    for (let key in folder.value) {
      if (key.startsWith(data.from)) {
        folder.value[key.replace(regex, data.to)] = folder.value[key].map(
          (v) => {
            v.pathname = v.pathname.replace(regex, data.to)
            return v
          },
        )
        delete folder.value[key]
      }
    }
    for (let key in file.value) {
      if (key.startsWith(data.from)) {
        file.value[key.replace(regex, data.to)] = file.value[key].map((v) => {
          v.pathname = v.pathname.replace(regex, data.to)
          return v
        })
        delete file.value[key]
      }
    }
  })
  socket.on('FolderDelete', (data: { pathname: string }) => {
    for (let key in folder.value) {
      if (key.startsWith(data.pathname)) {
        delete folder.value[key]
      }
    }
    for (let key in file.value) {
      if (key.startsWith(data.pathname)) {
        delete file.value[key]
      }
    }

    const arr = data.pathname.split('/').filter(Boolean)
    const path = consistantPath(arr.slice(0, -1))

    if (folder.value[path]) {
      folder.value[path] = folder.value[path].filter(
        (v) => v.pathname !== data.pathname,
      )
    }

    if (
      currentInfo.path.length >= consistantPath(arr).length &&
      currentInfo.path.startsWith(consistantPath(arr))
    ) {
      error.set({
        title: 'แจ้งเตือน',
        msg: 'ข้อมูลที่คุณกำลังเข้าถึงอยู่ถูกลบ',
      })
      goto()
    }
  })
  socket.on('FileUpload', (data: StorageFile) => {
    const arr = data.pathname.split('/').filter(Boolean)
    const path = consistantPath(arr.slice(0, -1))

    if (file.value[path]) {
      const idx = file.value[path].findIndex(
        (v) => v.pathname === data.pathname,
      )
      if (idx !== -1) file.value[path][idx] = data
      else file.value[path].push(data)

      file.value[path].sort((a, b) => a.pathname.localeCompare(b.pathname))
    }
  })
  socket.on('FileDelete', (data: { pathname: string }) => {
    const arr = data.pathname.split('/').filter(Boolean)
    const path = consistantPath(arr.slice(0, -1))

    if (file.value[path]) {
      file.value[path] = file.value[path].filter(
        (v) => v.pathname !== data.pathname,
      )
    }
  })
  socket.on('FileMove', (data: { from: StorageFile; to: StorageFile }) => {
    const arr = data.from.pathname.split('/').filter(Boolean)
    const path = consistantPath(arr.slice(0, -1))

    if (file.value[path]) {
      const idx = file.value[path].findIndex(
        (v) => v.pathname === data.from.pathname,
      )
      if (idx !== -1) file.value[path][idx] = data.to
    }
  })
  socket.on('FileUpdate', (data: StorageFile) => {
    const arr = data.pathname.split('/').filter(Boolean)
    const path = consistantPath(arr.slice(0, -1))

    if (file.value[path]) {
      const idx = file.value[path].findIndex(
        (v) => v.pathname === data.pathname,
      )
      if (idx !== -1) file.value[path][idx] = data
    }
  })
  socket.on('FileUploadRequest', (data: StorageFile) => {
    const arr = data.pathname.split('/').filter(Boolean)
    const path = consistantPath(arr.slice(0, -1))

    if (file.value[path]) {
      const idx = file.value[path].findIndex(
        (v) => v.pathname === data.pathname,
      )

      if (idx !== -1) file.value[path][idx] = data
      else file.value[path].push(data)

      file.value[path].sort((a, b) => a.pathname.localeCompare(b.pathname))
    }
  })

  async function createFolder(name: string, path: string = currentInfo.path) {
    loader.show()
    if (
      folder.value[consistantPath(path)]?.findIndex((v) => v.name === name) !==
      -1
    ) {
      error.set({
        title: 'แจ้งเตือน',
        msg: `พบชื่อ \"${name}\" ซ้ำในระบบ`,
      })
    } else {
      const arrayPath: string[] = path.split('/').filter(Boolean)
      await api.post(`${import.meta.env.VITE_API_ENDPOINT}storage/folder`, {
        path: arrayPath,
        name: name,
      })
    }
    loader.hide()
  }
  async function editFolder(name: string, path: string) {
    loader.show()
    const arrayPath: string[] = path.split('/').filter(Boolean)
    const beforeName = arrayPath.pop()

    await api.put(`${import.meta.env.VITE_API_ENDPOINT}storage/folder`, {
      from: {
        name: beforeName,
        path: arrayPath,
      },
      to: {
        name: name,
        path: arrayPath,
      },
    })
    loader.hide()
  }
  async function deleteFolder(path: string) {
    loader.show()
    await api.delete<(typeof file.value)[string]>(
      `${import.meta.env.VITE_API_ENDPOINT}storage/folder`,
      { data: { path: path.split('/').filter(Boolean) } },
    )
    loader.hide()
  }

  type FileMetadata = {
    title?: string
    description?: string
    category?: string[]
    keyword?: string[]
  }
  async function createFile(
    file: File,
    data: FileMetadata,
    path: string = currentInfo.path,
  ) {
    const arr = path.split('/').filter(Boolean)

    if (arr.length < 3) return // the system only allow file to live in level 3 and 4

    loader.show()
    const res = await api.post(
      `${import.meta.env.VITE_API_ENDPOINT}storage/file`,
      {
        path: arr,
        file: file.name,
        ...data,
      },
    )
    if (res && res.status === 200 && res.data && res.data.uploadUrl) {
      await axios
        .put(res.data.uploadUrl, file, {
          headers: { 'Content-Type': file.type },
          onUploadProgress: (e) => console.log(e),
        })
        .catch((e) => console.error(e))
    }
    loader.hide()
  }
  async function updateFile(pathname: string, data: FileMetadata, file?: File) {
    const arr = pathname.split('/')

    if (arr.length < 4) return // the system only allow file to live in level 3 and 4

    loader.show()

    const srcFile = arr.pop()

    const res = await api.put(
      `${import.meta.env.VITE_API_ENDPOINT}storage/file`,
      {
        ...data,
        from: {
          file: srcFile,
          path: arr,
        },
        to: file?.name
          ? {
              file: file.name,
              path: arr,
            }
          : undefined,
        upload: !!file,
      },
    )
    if (res && res.status === 200 && res.data && res.data.uploadUrl) {
      await axios
        .put(res.data.uploadUrl, file, {
          headers: { 'Content-Type': file?.type },
          onUploadProgress: (e) => console.log(e),
        })
        .catch((e) => console.error(e))
    }
    loader.hide()
  }
  async function deleteFile(pathname: string) {
    const arr = pathname.split('/')

    if (arr.length < 4) return // the system only allow file to live in level 3 and 4

    loader.show()

    await api.delete(`${import.meta.env.VITE_API_ENDPOINT}storage/file`, {
      data: { path: arr.slice(0, -1), file: arr[arr.length - 1] },
    })
    loader.hide()
  }

  return {
    // information
    currentInfo,
    folder,
    file,
    tree,
    // fetch
    getStorage,
    getStorageFile,
    // traverse
    goto,
    gotoParent,
    // operation
    createFolder,
    editFolder,
    deleteFolder,
    createFile,
    updateFile,
    deleteFile,
    constructUrl,
    consistantPath,
  }
})

export default useStorage
