import { jsonResponse, errorResponse } from '../utils/cors'
import { WORKING_DIR } from '../config'

// Execute git command and return stdout
async function execGit(args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number }> {
  const proc = Bun.spawn(['git', ...args], {
    cwd: WORKING_DIR,
    stdout: 'pipe',
    stderr: 'pipe'
  })
  const stdout = await new Response(proc.stdout).text()
  const stderr = await new Response(proc.stderr).text()
  const exitCode = await proc.exited
  return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode }
}

// Parse git status porcelain output
function parseStatus(output: string): { staged: any[]; unstaged: any[]; untracked: string[] } {
  const staged: any[] = []
  const unstaged: any[] = []
  const untracked: string[] = []

  if (!output) return { staged, unstaged, untracked }

  const lines = output.split('\n').filter(Boolean)

  for (const line of lines) {
    const indexStatus = line[0]
    const workTreeStatus = line[1]
    const path = line.slice(3)

    // Untracked
    if (indexStatus === '?' && workTreeStatus === '?') {
      untracked.push(path)
      continue
    }

    // Staged changes
    if (indexStatus !== ' ' && indexStatus !== '?') {
      staged.push({
        path,
        status: parseStatusCode(indexStatus),
        staged: true
      })
    }

    // Unstaged changes
    if (workTreeStatus !== ' ' && workTreeStatus !== '?') {
      unstaged.push({
        path,
        status: parseStatusCode(workTreeStatus),
        staged: false
      })
    }
  }

  return { staged, unstaged, untracked }
}

function parseStatusCode(code: string): string {
  const map: Record<string, string> = {
    'M': 'modified',
    'A': 'added',
    'D': 'deleted',
    'R': 'renamed',
    'C': 'copied',
    'U': 'unmerged',
    '?': 'untracked'
  }
  return map[code] || 'unknown'
}

// GET /api/git/status
export async function handleGitStatus() {
  const { stdout: branch } = await execGit(['branch', '--show-current'])
  const { stdout: statusOutput, exitCode } = await execGit(['status', '--porcelain', '-uall'])

  if (exitCode !== 0) {
    return errorResponse('Not a git repository', 400)
  }

  const { staged, unstaged, untracked } = parseStatus(statusOutput)

  // Get ahead/behind info
  let ahead = 0
  let behind = 0
  const { stdout: trackingInfo } = await execGit(['rev-list', '--left-right', '--count', '@{upstream}...HEAD'])
  if (trackingInfo) {
    const parts = trackingInfo.split('\t')
    if (parts.length === 2) {
      behind = parseInt(parts[0]) || 0
      ahead = parseInt(parts[1]) || 0
    }
  }

  return jsonResponse({
    branch,
    staged,
    unstaged,
    untracked,
    ahead,
    behind
  })
}

// GET /api/git/diff
export async function handleGitDiff(url: URL) {
  const staged = url.searchParams.get('staged') === 'true'
  const file = url.searchParams.get('file')

  const args = ['diff']
  if (staged) args.push('--cached')
  if (file) args.push('--', file)

  const { stdout, exitCode } = await execGit(args)

  if (exitCode !== 0) {
    return errorResponse('Failed to get diff', 400)
  }

  // Parse diff into structured format
  const files = parseDiff(stdout)

  return jsonResponse({
    raw: stdout,
    files
  })
}

// Parse unified diff format
function parseDiff(diffOutput: string): any[] {
  if (!diffOutput) return []

  const files: any[] = []
  const fileDiffs = diffOutput.split(/(?=^diff --git)/m).filter(Boolean)

  for (const fileDiff of fileDiffs) {
    const lines = fileDiff.split('\n')
    const headerMatch = lines[0]?.match(/^diff --git a\/(.+) b\/(.+)$/)
    if (!headerMatch) continue

    const oldPath = headerMatch[1]
    const newPath = headerMatch[2]

    // Determine status
    let status = 'modified'
    if (lines.some(l => l.startsWith('new file'))) status = 'added'
    if (lines.some(l => l.startsWith('deleted file'))) status = 'deleted'
    if (lines.some(l => l.startsWith('rename '))) status = 'renamed'

    // Check if binary
    const isBinary = lines.some(l => l.includes('Binary files'))

    // Parse hunks
    const hunks: any[] = []
    let currentHunk: any = null

    for (const line of lines) {
      const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/)
      if (hunkMatch) {
        if (currentHunk) hunks.push(currentHunk)
        currentHunk = {
          oldStart: parseInt(hunkMatch[1]),
          oldLines: parseInt(hunkMatch[2]) || 1,
          newStart: parseInt(hunkMatch[3]),
          newLines: parseInt(hunkMatch[4]) || 1,
          header: hunkMatch[5].trim(),
          lines: []
        }
        continue
      }

      if (currentHunk && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
        currentHunk.lines.push({
          type: line.startsWith('+') ? 'add' : line.startsWith('-') ? 'delete' : 'context',
          content: line.slice(1)
        })
      }
    }
    if (currentHunk) hunks.push(currentHunk)

    files.push({
      path: newPath,
      oldPath: oldPath !== newPath ? oldPath : undefined,
      status,
      isBinary,
      hunks
    })
  }

  return files
}

// GET /api/git/log
export async function handleGitLog(url: URL) {
  const limit = parseInt(url.searchParams.get('limit') || '50')
  const offset = parseInt(url.searchParams.get('offset') || '0')
  const author = url.searchParams.get('author')
  const since = url.searchParams.get('since')

  const args = [
    'log',
    `--skip=${offset}`,
    `-n${limit}`,
    '--format=%H|%h|%an|%ae|%at|%s'
  ]
  if (author) args.push(`--author=${author}`)
  if (since) args.push(`--since=${since}`)

  const { stdout, exitCode } = await execGit(args)

  if (exitCode !== 0) {
    return errorResponse('Failed to get log', 400)
  }

  if (!stdout) {
    return jsonResponse([])
  }

  const commits = stdout.split('\n').filter(Boolean).map(line => {
    const [sha, shortSha, author, email, timestamp, message] = line.split('|')
    return {
      sha,
      shortSha,
      author,
      email,
      timestamp: parseInt(timestamp),
      message
    }
  })

  return jsonResponse(commits)
}

// GET /api/git/log/:sha
export async function handleGitLogCommit(sha: string) {
  const { stdout: details, exitCode } = await execGit([
    'show',
    sha,
    '--format=%H|%h|%an|%ae|%at|%s%n%b',
    '--stat'
  ])

  if (exitCode !== 0) {
    return errorResponse('Commit not found', 404)
  }

  const lines = details.split('\n')
  const [sha_, shortSha, author, email, timestamp, message] = lines[0].split('|')

  // Get body (everything between first line and stats)
  let body = ''
  let statsStart = lines.findIndex(l => l.match(/^\s+\d+ files? changed/))
  if (statsStart === -1) statsStart = lines.length
  body = lines.slice(1, statsStart).join('\n').trim()

  // Get diff for this commit
  const { stdout: diffOutput } = await execGit(['show', sha, '--format='])
  const files = parseDiff(diffOutput)

  return jsonResponse({
    sha: sha_,
    shortSha,
    author,
    email,
    timestamp: parseInt(timestamp),
    message,
    body,
    files
  })
}

// POST /api/git/compare
export async function handleGitCompare(req: Request) {
  const body = await req.json()
  const { base, head } = body

  if (!base || !head) {
    return errorResponse('base and head are required', 400)
  }

  const { stdout, exitCode, stderr } = await execGit(['diff', `${base}...${head}`])

  if (exitCode !== 0) {
    return errorResponse(stderr || 'Failed to compare', 400)
  }

  const files = parseDiff(stdout)

  // Calculate stats
  let additions = 0
  let deletions = 0
  for (const file of files) {
    for (const hunk of file.hunks || []) {
      for (const line of hunk.lines || []) {
        if (line.type === 'add') additions++
        if (line.type === 'delete') deletions++
      }
    }
  }

  return jsonResponse({
    base,
    head,
    files,
    stats: {
      filesChanged: files.length,
      additions,
      deletions
    }
  })
}

// GET /api/git/branches
export async function handleGitBranches() {
  const { stdout, exitCode } = await execGit(['branch', '-a', '--format=%(refname:short)|%(HEAD)'])

  if (exitCode !== 0) {
    return errorResponse('Failed to get branches', 400)
  }

  if (!stdout) {
    return jsonResponse([])
  }

  const branches = stdout.split('\n').filter(Boolean).map(line => {
    const [name, isCurrent] = line.split('|')
    return {
      name: name.trim(),
      isCurrent: isCurrent === '*',
      isRemote: name.startsWith('origin/')
    }
  })

  return jsonResponse(branches)
}

// GET /api/git/branch/current
export async function handleGitCurrentBranch() {
  const { stdout, exitCode } = await execGit(['branch', '--show-current'])

  if (exitCode !== 0) {
    return errorResponse('Not a git repository', 400)
  }

  return jsonResponse({ branch: stdout })
}

// GET /api/git/tree - Get project file tree
export async function handleGitTree(url: URL) {
  // Use git ls-tree for tracked files, combined with untracked
  const { stdout: trackedOutput, exitCode } = await execGit([
    'ls-tree', '-r', '--name-only', 'HEAD'
  ])

  if (exitCode !== 0) {
    return errorResponse('Failed to get file tree', 400)
  }

  // Get untracked files too
  const { stdout: untrackedOutput } = await execGit([
    'ls-files', '--others', '--exclude-standard'
  ])

  const trackedFiles = trackedOutput.split('\n').filter(Boolean)
  const untrackedFiles = untrackedOutput.split('\n').filter(Boolean)
  const allFiles = [...new Set([...trackedFiles, ...untrackedFiles])].sort()

  // Build tree structure
  const tree = buildFileTree(allFiles)

  return jsonResponse(tree)
}

// Build hierarchical tree from flat file paths
function buildFileTree(files: string[]): any[] {
  const root: any = { children: {} }

  // Build nested structure
  for (const filePath of files) {
    const parts = filePath.split('/')
    let current = root

    for (let i = 0; i < parts.length; i++) {
      const name = parts[i]
      const isFile = i === parts.length - 1
      const currentPath = parts.slice(0, i + 1).join('/')

      if (!current.children[name]) {
        current.children[name] = {
          name,
          path: currentPath,
          type: isFile ? 'file' : 'directory',
          children: isFile ? undefined : {}
        }
      }

      current = current.children[name]
    }
  }

  // Convert to array format recursively
  function toArray(node: any): any[] {
    if (!node.children) return []

    const items = Object.values(node.children) as any[]

    // Convert children objects to arrays
    for (const item of items) {
      if (item.type === 'directory' && item.children) {
        item.children = toArray(item)
      }
    }

    // Sort: directories first, then alphabetically
    return items.sort((a, b) => {
      if (a.type !== b.type) {
        return a.type === 'directory' ? -1 : 1
      }
      return a.name.localeCompare(b.name)
    })
  }

  return toArray(root)
}

// GET /api/git/file - Get file content
export async function handleGitFile(url: URL) {
  const filePath = url.searchParams.get('path')

  if (!filePath) {
    return errorResponse('path is required', 400)
  }

  // Security: prevent directory traversal
  if (filePath.includes('..') || filePath.startsWith('/')) {
    return errorResponse('Invalid path', 400)
  }

  const fullPath = `${WORKING_DIR}/${filePath}`

  try {
    const file = Bun.file(fullPath)
    const exists = await file.exists()

    if (!exists) {
      return errorResponse('File not found', 404)
    }

    // Check if binary
    const content = await file.text()
    const isBinary = /[\x00-\x08\x0E-\x1F]/.test(content.slice(0, 1000))

    if (isBinary) {
      return jsonResponse({
        path: filePath,
        isBinary: true,
        content: null,
        size: file.size
      })
    }

    // Get file extension for syntax highlighting hint
    const ext = filePath.split('.').pop()?.toLowerCase() || ''

    return jsonResponse({
      path: filePath,
      isBinary: false,
      content,
      size: file.size,
      extension: ext
    })
  } catch (e: any) {
    return errorResponse(e.message, 500)
  }
}
