import { App } from 'octokit'
import yaml from 'yaml'
import type { Project } from '~/types'

export default defineEventHandler(async (event) => {
  const body = await readBody<{ project: Project, image?: { type: string, data: string } }>(event)
  const { appId, privateKey, installationId, baseBranch, owner, repo } = useRuntimeConfig().app.github

  const id = (body.project.id && body.project.id === body.project.name.toLowerCase().replace(/\s+/g, '-'))
    ? body.project.id
    : body.project.name.toLowerCase().replace(/\s+/g, '-')

  const yamlProject = yaml.stringify({
    ...body.project,
    id,
  })

  const app = new App({
    appId,
    privateKey,
  })
  await app.octokit.rest.apps.getAuthenticated()
  const octokit = await app.getInstallationOctokit(installationId)

  const newBranchName = `${id}-project-update-${Date.now()}`
  const commitMessage = `${body.project.id
    ? `Updating the project: ${body.project.id}`
    : `Initiating the creation of project: ${body.project.name}`}`

  const files = [
    {
      path: `src/projects/${id}/index.yaml`,
      content: yamlProject,
      encoding: 'utf-8',
    },
  ]

  if (body.image?.data && body.image?.type) {
    files.push(
      {
        path: `src/projects/${id}/logo.${body.image.type.split('/')[1]}`,
        content: body.image.data,
        encoding: 'base64',
      },
    )
  }

  async function createBranch(owner: string, repo: string, newBranchName: string, baseBranch: string) {
    const { data: baseBranchData } = await octokit.rest.git.getRef({
      owner,
      repo,
      ref: `heads/${baseBranch}`,
    })

    await octokit.rest.git.createRef({
      owner,
      repo,
      ref: `refs/heads/${newBranchName}`,
      sha: baseBranchData.object.sha,
    })
  }

  async function deleteOldProjectFolder(owner: string, repo: string, branch: string, oldFolderPath: string) {
    const { data: latestCommit } = await octokit.rest.repos.getCommit({
      owner,
      repo,
      ref: branch,
    })

    const { data: baseTree } = await octokit.rest.git.getTree({
      owner,
      repo,
      tree_sha: latestCommit.commit.tree.sha,
      recursive: 'true',
    })

    // Log to verify the tree structure
    console.log('Base Tree:', baseTree.tree.map(item => item.path))

    // Filter out the old folder from the tree
    const newTreeItems = baseTree.tree.filter(item => !item.path?.startsWith(oldFolderPath))
    console.log('New Tree Items (after filtering):', newTreeItems.map(item => item.path))

    if (newTreeItems.length === baseTree.tree.length) {
      console.log(`No items found to delete in the folder: ${oldFolderPath}`)
      return
    }

    const newTreeMappedItems = newTreeItems.map(item => ({
      path: item.path,
      mode: item.mode as '100644' | '100755' | '040000' | '160000' | '120000' | undefined,
      type: item.type as 'tree' | 'blob' | 'commit' | undefined,
      sha: item.sha,
    }))

    // Create a new tree without the old folder
    const { data: newTree } = await octokit.rest.git.createTree({
      owner,
      repo,
      tree: newTreeMappedItems,
      base_tree: baseTree.sha,
    })

    console.log('New Tree SHA:', newTree.sha)

    // Create a new commit with the new tree
    const { data: newCommit } = await octokit.rest.git.createCommit({
      owner,
      repo,
      message: `Deleting old project folder: ${oldFolderPath}`,
      tree: newTree.sha,
      parents: [latestCommit.sha],
    })

    console.log('New Commit SHA:', newCommit.sha)

    // Update the reference to point to the new commit
    await octokit.rest.git.updateRef({
      owner,
      repo,
      ref: `heads/${branch}`,
      sha: newCommit.sha,
    })
  }

  async function commitChangesToNewBranch(
    owner: string,
    repo: string,
    newBranch: string,
    message: string,
    files: { path: string, content: string, encoding: string }[],
    deletedFiles: string[] = [],
  ) {
    const { data: latestCommit } = await octokit.rest.repos.getCommit({
      owner,
      repo,
      ref: newBranch,
    })

    const { data: baseTree } = await octokit.rest.git.getTree({
      owner,
      repo,
      tree_sha: latestCommit.commit.tree.sha,
    })

    const blobs = await Promise.all(files.map(async (file) => {
      const { data: blob } = await octokit.rest.git.createBlob({
        owner,
        repo,
        content: file.content,
        encoding: file.encoding,
      })
      return {
        path: file.path,
        mode: '100644' as const,
        type: 'blob' as const,
        sha: blob.sha,
      }
    }))

    const deletions = deletedFiles.map(filePath => ({
      path: filePath,
      mode: '100644' as const,
      type: 'blob' as const,
      sha: null,
    }))

    const { data: newTree } = await octokit.rest.git.createTree({
      owner,
      repo,
      base_tree: baseTree.sha,
      tree: [...blobs, ...deletions],
    })

    const { data: newCommit } = await octokit.rest.git.createCommit({
      owner,
      repo,
      message,
      tree: newTree.sha,
      parents: [latestCommit.sha],
    })

    await octokit.rest.git.updateRef({
      owner,
      repo,
      ref: `heads/${newBranch}`,
      sha: newCommit.sha,
    })
  }

  async function createPullRequest(owner: string, repo: string, head: string, base: string, title: string, body: string) {
    const { data: pullRequest } = await octokit.rest.pulls.create({
      owner,
      repo,
      title,
      head,
      base,
      body,
    })

    return pullRequest
  }

  try {
    await createBranch(owner, repo, newBranchName, baseBranch)
    console.log(`Branch ${newBranchName} created successfully!`)

    const deletedFiles = []
    if (body.project.id && body.project.id !== body.project.name.toLowerCase().replace(/\s+/g, '-')) {
      const oldId = body.project.id
      const oldFolderPath = `src/projects/${oldId}`
      await deleteOldProjectFolder(owner, repo, newBranchName, oldFolderPath)
      console.log(`Old project folder ${oldFolderPath} deleted successfully!`)
      deletedFiles.push(oldFolderPath)
    }

    await commitChangesToNewBranch(owner, repo, newBranchName, commitMessage, files, deletedFiles)
    console.log(`Changes committed to branch ${newBranchName} successfully!`)

    const pullRequestData = await createPullRequest(
      owner,
      repo,
      newBranchName,
      baseBranch,
      `${body.project.id ? `Update project: ${body.project.id}` : `Create project: ${body.project.name}`}`,
      `${body.project.id ? `Updating the project: ${body.project.id}` : `Initiating the creation of project: ${body.project.name}`}`,
    )
    console.log('Pull request created:', pullRequestData)
  }
  catch (error) {
    console.error('Error during GitHub operations:', error)
  }
})