import type { GitlabConfig } from './useGitlabStorage'
import type { FileMapping } from './zip/createDefinitionZip'
import { createDefinitionZip } from './zip/createDefinitionZip'

const dataFolder = 'assets/definition/'
const rawFilePath = 'assets/definition.zip'
const encodedFilePath = encodeURIComponent(rawFilePath)

export type Project = {
  id: string
  name: string
  description: string
}

export type Branch = {
  name: string
}

type Action = {
  action: string
  file_path: string
  encoding?: string
  content?: string
}

const getHeader = (token?: string): HeadersInit =>
  token
    ? {
        'PRIVATE-TOKEN': token,
      }
    : {}

export const validateGitLabGroupToken = async (
  config: GitlabConfig,
  token?: string
): Promise<number | undefined> => {
  if (!config.groupPath) return undefined

  const headers = getHeader(token)

  try {
    const response = await fetch(
      `${config.gitlabHost}/api/v4/groups/${encodeURIComponent(config.groupPath)}`,
      {
        method: 'GET',
        headers,
      }
    )

    if (!response.ok) {
      throw new Error(`Error fetching projects: ${response.status}`)
    }

    return (await response.json()).id
  } catch {
    return undefined
  }
}

export const fetchProjects = async (
  config: GitlabConfig
): Promise<Project[]> => {
  if (!config.groupPath) return []

  const headers = getHeader(config.token)
  const response = await fetch(
    `${config.gitlabHost}/api/v4/groups/${encodeURIComponent(config.groupPath)}/projects`,
    {
      method: 'GET',
      headers,
    }
  )
  if (!response.ok) {
    throw new Error(`Error fetching projects: ${response.status}`)
  }
  return await response.json()
}

export const fetchBranches = async (
  config: GitlabConfig,
  projectId: string
): Promise<Branch[]> => {
  const headers = getHeader(config.token)

  const response = await fetch(
    `${config.gitlabHost}/api/v4/projects/${projectId}/repository/branches`,
    {
      method: 'GET',
      headers,
    }
  )
  if (!response.ok) {
    throw new Error(`Error fetching branches: ${response.status}`)
  }
  return await response.json()
}

export const downloadDefinition = async (
  config: GitlabConfig,
  projectId: string,
  branch: string
) => {
  const headers = getHeader(config.token)
  const response = await fetch(
    `${config.gitlabHost}/api/v4/projects/${projectId}/repository/files/${encodedFilePath}/raw?ref=${branch}`,
    {
      method: 'GET',
      headers,
    }
  )
  if (!response.ok) {
    throw new Error(`Error fetching definition: ${response.status}`)
  }
  return await response.blob()
}

export const createProject = async (
  config: GitlabConfig,
  name: string,
  description: string,
  visibility: string
): Promise<Project> => {
  const headers = getHeader(config.token)

  const response = await fetch(`${config.gitlabHost}/api/v4/projects`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...headers,
    },
    body: JSON.stringify({
      name,
      description,
      visibility,
      namespace_id: config.groupId,
    }),
  })

  if (!response.ok) {
    throw new Error(`Error creating project: ${response.status}`)
  }

  const data = await response.json()
  return data
}

export const createBranch = async (
  config: GitlabConfig,
  ref: string,
  branchName: string
): Promise<Branch> => {
  const headers = getHeader(config.token)

  const response = await fetch(
    `${config.gitlabHost}/api/v4/projects/${config.project?.id}/repository/branches`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...headers,
      },
      body: JSON.stringify({
        branch: branchName,
        ref,
      }),
    }
  )

  if (!response.ok) {
    throw new Error(`Error creating branch: ${response.status}`)
  }

  const data = await response.json()
  return data
}

const blobToBase64 = (blob: Blob) =>
  new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onloadend = () => resolve((reader.result as string).split(',')[1])
    reader.onerror = err => reject(err)
    reader.readAsDataURL(blob)
  })

export const createGitlabActionStructure = async (
  fileMapping: FileMapping[],
  fileNames?: string[]
) => {
  const zip = await createDefinitionZip(fileMapping)

  const fileActions = [
    ...(await Promise.all(
      fileMapping.map(async file => ({
        action: fileNames?.find(name => file.name === name)
          ? 'update'
          : 'create',
        file_path: `${dataFolder}${file.name}`,
        encoding: typeof file.content === 'string' ? 'text' : 'base64',
        content:
          typeof file.content === 'string'
            ? file.content
            : await blobToBase64(file.content),
      }))
    )),
    ...(fileNames
      ?.filter(name => !fileMapping.find(file => file.name === name))
      .map(name => ({
        action: 'delete',
        file_path: `${dataFolder}${name}`,
      })) || []),
    {
      action: fileNames ? 'update' : 'create',
      file_path: rawFilePath,
      encoding: 'base64',
      content: await blobToBase64(zip),
    },
  ]

  return fileActions
}

export const commitFiles = async (
  config: GitlabConfig,
  branch: string,
  actions: Action[],
  commitMessage?: string
) => {
  const headers = getHeader(config.token)

  const response = await fetch(
    `${config.gitlabHost}/api/v4/projects/${config.project?.id}/repository/commits`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...headers,
      },
      body: JSON.stringify({
        branch,
        commit_message: commitMessage,
        actions,
      }),
    }
  )

  if (!response.ok) {
    throw new Error(`Error committing file: ${response.status}`)
  }

  const data = await response.json()
  return data
}
