/**
 * GNAP Sync Engine — push MC tasks to a Git-Native Agent Protocol repo.
 *
 * SQLite remains the primary store. The GNAP repo is an optional sync target
 * following the same pattern as `github-sync-engine.ts`.
 *
 * Phase 1: MC → GNAP only (push). Pull/bidirectional sync is Phase 2.
 */

import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import { logger } from '@/lib/logger'

// ── Status / priority mapping ──────────────────────────────────

const MC_TO_GNAP_STATUS: Record<string, string> = {
  pending: 'backlog',
  inbox: 'backlog',
  assigned: 'ready',
  ready: 'ready',
  in_progress: 'in_progress',
  review: 'review',
  quality_review: 'review',
  completed: 'done',
  done: 'done',
  blocked: 'blocked',
  cancelled: 'cancelled',
}

const GNAP_TO_MC_STATUS: Record<string, string> = {
  backlog: 'inbox',
  ready: 'assigned',
  in_progress: 'in_progress',
  review: 'review',
  done: 'done',
  blocked: 'blocked',
  cancelled: 'cancelled',
}

const MC_TO_GNAP_PRIORITY: Record<string, string> = {
  low: 'low',
  medium: 'medium',
  high: 'high',
  critical: 'critical',
  urgent: 'critical',
}

export function mcStatusToGnap(status: string): string {
  return MC_TO_GNAP_STATUS[status] || 'backlog'
}

export function gnapStatusToMc(state: string): string {
  return GNAP_TO_MC_STATUS[state] || 'inbox'
}

export function mcPriorityToGnap(priority: string): string {
  return MC_TO_GNAP_PRIORITY[priority] || 'medium'
}

// ── GNAP task JSON type ────────────────────────────────────────

export interface GnapTask {
  id: string
  title: string
  description: string
  state: string
  assignee: string
  priority: string
  tags: string[]
  created: string
  updated: string
  mc_id: number
  mc_project_id: number | null
}

// ── Git helpers ────────────────────────────────────────────────

function git(repoPath: string, args: string[]): string {
  try {
    return execFileSync('git', args, {
      cwd: repoPath,
      encoding: 'utf-8',
      timeout: 15_000,
      stdio: ['pipe', 'pipe', 'pipe'],
    }).trim()
  } catch (err: any) {
    const stderr = err.stderr?.toString?.() || ''
    throw new Error(`git ${args[0]} failed: ${stderr || err.message}`)
  }
}

function hasRemote(repoPath: string): boolean {
  try {
    const remotes = git(repoPath, ['remote'])
    return remotes.length > 0
  } catch {
    return false
  }
}

function hasChanges(repoPath: string): boolean {
  try {
    const status = git(repoPath, ['status', '--porcelain'])
    return status.length > 0
  } catch {
    return false
  }
}

// ── Core functions ─────────────────────────────────────────────

export function initGnapRepo(repoPath: string): void {
  fs.mkdirSync(path.join(repoPath, 'tasks'), { recursive: true })

  const versionFile = path.join(repoPath, 'version')
  if (!fs.existsSync(versionFile)) {
    fs.writeFileSync(versionFile, '1\n')
  }

  const agentsFile = path.join(repoPath, 'agents.json')
  if (!fs.existsSync(agentsFile)) {
    fs.writeFileSync(agentsFile, JSON.stringify({ agents: [] }, null, 2) + '\n')
  }

  // Init git if not already a repo
  const gitDir = path.join(repoPath, '.git')
  if (!fs.existsSync(gitDir)) {
    git(repoPath, ['init'])
    git(repoPath, ['add', '.'])
    git(repoPath, ['commit', '-m', 'Initialize GNAP repository'])
  }

  logger.info({ repoPath }, 'GNAP repo initialized')
}

export interface McTask {
  id: number
  title: string
  description?: string | null
  status: string
  priority: string
  assigned_to?: string | null
  tags?: string[] | string | null
  created_at?: number | null
  updated_at?: number | null
  project_id?: number | null
}

function taskToGnapJson(task: McTask): GnapTask {
  const tags = Array.isArray(task.tags)
    ? task.tags
    : (typeof task.tags === 'string' ? JSON.parse(task.tags || '[]') : [])

  return {
    id: `mc-${task.id}`,
    title: task.title,
    description: task.description || '',
    state: mcStatusToGnap(task.status),
    assignee: task.assigned_to || '',
    priority: mcPriorityToGnap(task.priority),
    tags,
    created: task.created_at
      ? new Date(task.created_at * 1000).toISOString()
      : new Date().toISOString(),
    updated: task.updated_at
      ? new Date(task.updated_at * 1000).toISOString()
      : new Date().toISOString(),
    mc_id: task.id,
    mc_project_id: task.project_id ?? null,
  }
}

export function pushTaskToGnap(task: McTask, repoPath: string): void {
  const tasksDir = path.join(repoPath, 'tasks')
  fs.mkdirSync(tasksDir, { recursive: true })

  const gnapTask = taskToGnapJson(task)
  const filePath = path.join(tasksDir, `${gnapTask.id}.json`)
  fs.writeFileSync(filePath, JSON.stringify(gnapTask, null, 2) + '\n')

  git(repoPath, ['add', path.relative(repoPath, filePath)])

  if (hasChanges(repoPath)) {
    git(repoPath, ['commit', '-m', `Update task ${gnapTask.id}: ${task.title}`])
  }

  if (hasRemote(repoPath)) {
    try {
      git(repoPath, ['push'])
    } catch (err) {
      logger.warn({ err, repoPath }, 'GNAP push to remote failed (continuing)')
    }
  }
}

export function removeTaskFromGnap(taskId: number, repoPath: string): void {
  const filePath = path.join(repoPath, 'tasks', `mc-${taskId}.json`)

  if (!fs.existsSync(filePath)) return

  git(repoPath, ['rm', path.relative(repoPath, filePath)])

  if (hasChanges(repoPath)) {
    git(repoPath, ['commit', '-m', `Remove task mc-${taskId}`])
  }

  if (hasRemote(repoPath)) {
    try {
      git(repoPath, ['push'])
    } catch (err) {
      logger.warn({ err, repoPath }, 'GNAP push to remote failed (continuing)')
    }
  }
}

export function pullTasksFromGnap(repoPath: string): GnapTask[] {
  const tasksDir = path.join(repoPath, 'tasks')
  if (!fs.existsSync(tasksDir)) return []

  // Pull remote changes first if available
  if (hasRemote(repoPath)) {
    try {
      git(repoPath, ['pull', '--rebase'])
    } catch (err) {
      logger.warn({ err, repoPath }, 'GNAP pull from remote failed (using local)')
    }
  }

  const files = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'))
  const tasks: GnapTask[] = []

  for (const file of files) {
    try {
      const content = fs.readFileSync(path.join(tasksDir, file), 'utf-8')
      tasks.push(JSON.parse(content))
    } catch (err) {
      logger.warn({ err, file }, 'Failed to parse GNAP task file')
    }
  }

  return tasks
}

export interface SyncResult {
  pushed: number
  pulled: number
  errors: string[]
  lastSync: string
}

export function syncGnap(repoPath: string): SyncResult {
  const result: SyncResult = {
    pushed: 0,
    pulled: 0,
    errors: [],
    lastSync: new Date().toISOString(),
  }

  // Pull remote if available
  if (hasRemote(repoPath)) {
    try {
      git(repoPath, ['pull', '--rebase'])
    } catch (err: any) {
      result.errors.push(`Pull failed: ${err.message}`)
    }
  }

  // Count local tasks
  const tasksDir = path.join(repoPath, 'tasks')
  if (fs.existsSync(tasksDir)) {
    result.pushed = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json')).length
  }

  // Push if remote available
  if (hasRemote(repoPath) && hasChanges(repoPath)) {
    try {
      git(repoPath, ['add', '.'])
      git(repoPath, ['commit', '-m', `Sync from Mission Control at ${result.lastSync}`])
      git(repoPath, ['push'])
    } catch (err: any) {
      result.errors.push(`Push failed: ${err.message}`)
    }
  }

  return result
}

export function getGnapStatus(repoPath: string): {
  initialized: boolean
  taskCount: number
  hasRemote: boolean
  remoteUrl: string
} {
  const tasksDir = path.join(repoPath, 'tasks')
  const initialized = fs.existsSync(path.join(repoPath, 'version'))
  const taskCount = initialized && fs.existsSync(tasksDir)
    ? fs.readdirSync(tasksDir).filter(f => f.endsWith('.json')).length
    : 0

  let remote = false
  let remoteUrl = ''
  if (initialized) {
    try {
      remote = hasRemote(repoPath)
      if (remote) {
        remoteUrl = git(repoPath, ['remote', 'get-url', 'origin'])
      }
    } catch { /* no remote */ }
  }

  return { initialized, taskCount, hasRemote: remote, remoteUrl }
}
