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

// ── Types ──

interface PermissionPayload {
  hook_event_name?: string
  session_id?: string
  tool_name?: string
  tool_input?: unknown
  agent_name?: string
  cwd?: string
  [key: string]: unknown
}

interface StopPayload {
  hook_event_name?: string
  session_id?: string
  permission_mode?: string
  stop_hook_active?: boolean
  transcript_messages?: unknown[]
  [key: string]: unknown
}

interface PendingRequest<T = unknown> {
  resolve: (result: T | null) => void
  timer: ReturnType<typeof setTimeout>
  payload: Record<string, unknown>
  type: 'permission' | 'plan'
  createdAt: number
}

// ── State ──

const pendingRequests = new Map<string, PendingRequest>()
const TIMEOUT_MS = 120_000

// ── Broadcast ──
// API server (4101) and sync server (4105) are separate Bun processes.
// broadcastFn works when called from the sync server process (same module instance).
// From the API server process, we fall back to HTTP POST → sync server /broadcast endpoint.

let broadcastFn: ((message: string) => void) | null = null

export function setHooksApprovalBroadcast(fn: (message: string) => void) {
  broadcastFn = fn
  console.log('[HooksApproval] broadcastFn SET (same process)')
}

function broadcastMessage(message: string) {
  if (broadcastFn) {
    console.log('[HooksApproval] Broadcasting via direct fn')
    broadcastFn(message)
    return
  }
  // Cross-process fallback: POST to sync server /broadcast endpoint
  console.log('[HooksApproval] Broadcasting via HTTP to sync server')
  fetch(`http://localhost:${PORT_GIT}/broadcast`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: message
  }).then(() => {
    console.log('[HooksApproval] Broadcast via /broadcast OK')
  }).catch(e => {
    console.error('[HooksApproval] Broadcast failed:', e.message)
  })
}

function generateId(prefix: string): string {
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
}

// Notify terminal server (4103) about approval lifecycle → broadcasts state patches to all clients
function notifyAddApproval(agent: string, approval: Record<string, unknown>) {
  fetch(`http://localhost:${PORT_TERMINAL}/add-approval`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ agent, approval })
  }).catch(e => console.error('[HooksApproval] Failed to notify add-approval:', e.message))
}

function notifyResolveApproval(requestId: string, decision: string) {
  fetch(`http://localhost:${PORT_TERMINAL}/resolve-approval`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ requestId, decision })
  }).catch(e => console.error('[HooksApproval] Failed to notify resolve-approval:', e.message))
}

// ── Permission (PreToolUse hook) ──

export async function handleHooksApprovalPermission(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json() as PermissionPayload
    const requestId = generateId('haperm')
    console.log(`[HooksApproval] Permission request ${requestId}: tool=${body.tool_name} agent=${body.agent_name} session=${body.session_id}`)

    // Broadcast to UI
    broadcastMessage(JSON.stringify({
      type: 'hooks-approval-permission',
      requestId,
      tool_name: body.tool_name,
      tool_input: body.tool_input,
      agent_name: body.agent_name,
      session_id: body.session_id,
      cwd: body.cwd,
      timestamp: Date.now()
    }))

    // Track in centralized session state → broadcasts patch to all clients
    notifyAddApproval(body.agent_name || 'claude', {
      requestId,
      type: 'permission',
      toolName: body.tool_name,
      toolInput: body.tool_input,
      cwd: body.cwd,
      timestamp: Date.now()
    })

    // Long-poll: wait for UI decision or timeout
    const result = await new Promise<unknown>((resolve) => {
      const timer = setTimeout(() => {
        pendingRequests.delete(requestId)
        resolve(null)
      }, TIMEOUT_MS)

      pendingRequests.set(requestId, {
        resolve,
        timer,
        payload: body as Record<string, unknown>,
        type: 'permission',
        createdAt: Date.now()
      })
    })

    // null = timeout, let Claude Code handle normally
    if (result === null) {
      console.log(`[HooksApproval] Permission ${requestId} timed out`)
      return jsonResponse({})
    }

    console.log(`[HooksApproval] Permission ${requestId} resolved:`, JSON.stringify(result))
    return jsonResponse(result)
  } catch (e) {
    console.error(`[HooksApproval] Permission error:`, e)
    return errorResponse('Invalid JSON body', 400)
  }
}

// ── Plan (Stop hook) ──

export async function handleHooksApprovalPlan(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json() as StopPayload
    const requestId = generateId('haplan')
    console.log(`[HooksApproval] Plan request ${requestId}: session=${body.session_id} mode=${body.permission_mode}`)

    // Extract last assistant message for display
    let lastAssistantText = ''
    if (Array.isArray(body.transcript_messages)) {
      for (let i = body.transcript_messages.length - 1; i >= 0; i--) {
        const msg = body.transcript_messages[i] as any
        if (msg?.role === 'assistant') {
          if (typeof msg.content === 'string') {
            lastAssistantText = msg.content
          } else if (Array.isArray(msg.content)) {
            lastAssistantText = msg.content
              .filter((b: any) => b.type === 'text')
              .map((b: any) => b.text)
              .join('\n')
          }
          break
        }
      }
    }

    // Broadcast to UI
    broadcastMessage(JSON.stringify({
      type: 'hooks-approval-plan',
      requestId,
      session_id: body.session_id,
      permission_mode: body.permission_mode,
      lastAssistantText,
      timestamp: Date.now()
    }))

    // Track in centralized session state → broadcasts patch to all clients
    notifyAddApproval(body.agent_name || 'claude', {
      requestId,
      type: 'plan',
      lastAssistantText,
      timestamp: Date.now()
    })

    // Long-poll
    const result = await new Promise<unknown>((resolve) => {
      const timer = setTimeout(() => {
        pendingRequests.delete(requestId)
        resolve(null)
      }, TIMEOUT_MS)

      pendingRequests.set(requestId, {
        resolve,
        timer,
        payload: body as Record<string, unknown>,
        type: 'plan',
        createdAt: Date.now()
      })
    })

    if (result === null) {
      console.log(`[HooksApproval] Plan ${requestId} timed out`)
      return jsonResponse({})
    }

    console.log(`[HooksApproval] Plan ${requestId} resolved:`, JSON.stringify(result))
    return jsonResponse(result)
  } catch (e) {
    console.error(`[HooksApproval] Plan error:`, e)
    return errorResponse('Invalid JSON body', 400)
  }
}

// ── Respond to permission ──

export async function handleHooksApprovalRespond(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json() as { requestId: string; decision: string; reason?: string }

    if (!body.requestId || !body.decision) {
      return errorResponse('Missing requestId or decision', 400)
    }

    if (!['allow', 'allowAlways', 'deny'].includes(body.decision)) {
      return errorResponse('Decision must be "allow", "allowAlways", or "deny"', 400)
    }

    const pending = pendingRequests.get(body.requestId)
    if (!pending || pending.type !== 'permission') {
      console.warn(`[HooksApproval] Respond permission: ${body.requestId} not found (expired?)`)
      return errorResponse('Permission request not found or expired', 404)
    }

    console.log(`[HooksApproval] Responding permission ${body.requestId}: ${body.decision}${body.reason ? ' reason=' + body.reason : ''}`)
    clearTimeout(pending.timer)
    pendingRequests.delete(body.requestId)

    // Notify all clients that this approval was resolved
    notifyResolveApproval(body.requestId, body.decision)

    // Build hookSpecificOutput per PermissionRequest docs
    let hookOutput: Record<string, unknown>

    if (body.decision === 'allow') {
      hookOutput = {
        hookSpecificOutput: {
          hookEventName: 'PermissionRequest',
          decision: { behavior: 'allow' }
        }
      }
    } else if (body.decision === 'allowAlways') {
      // Allow + add tool to allowed permissions for session
      const toolName = pending.payload.tool_name as string || '*'
      hookOutput = {
        hookSpecificOutput: {
          hookEventName: 'PermissionRequest',
          decision: { behavior: 'allow' },
          allowedPermissions: [{ tool_name: toolName }]
        }
      }
    } else {
      // deny — with optional custom message
      hookOutput = {
        hookSpecificOutput: {
          hookEventName: 'PermissionRequest',
          decision: {
            behavior: 'deny',
            message: body.reason || 'Denied via UI'
          }
        }
      }
    }

    pending.resolve(hookOutput)

    return jsonResponse({ success: true, requestId: body.requestId, decision: body.decision })
  } catch (e) {
    return errorResponse('Invalid JSON body', 400)
  }
}

// ── Respond to plan ──

export async function handleHooksApprovalRespondPlan(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json() as { requestId: string; decision: 'approve' | 'reject' | 'edit'; reason?: string }

    if (!body.requestId || !body.decision) {
      return errorResponse('Missing requestId or decision', 400)
    }

    if (!['approve', 'reject', 'edit'].includes(body.decision)) {
      return errorResponse('Decision must be "approve", "reject", or "edit"', 400)
    }

    const pending = pendingRequests.get(body.requestId)
    if (!pending || pending.type !== 'plan') {
      console.warn(`[HooksApproval] Respond plan: ${body.requestId} not found (expired?)`)
      return errorResponse('Plan request not found or expired', 404)
    }

    console.log(`[HooksApproval] Responding plan ${body.requestId}: ${body.decision}`)
    clearTimeout(pending.timer)
    pendingRequests.delete(body.requestId)

    // Notify all clients that this approval was resolved
    notifyResolveApproval(body.requestId, body.decision)

    // Build Stop hook output per docs:
    // "approve" plan = "block" stop (so Claude continues to implement)
    // "reject" plan = let Claude stop (empty response)
    // "edit" = "block" stop with user instructions as reason
    let stopResult: Record<string, unknown>

    if (body.decision === 'approve') {
      stopResult = { decision: 'block', reason: 'Plan approved via UI. Proceed with implementation.' }
    } else if (body.decision === 'reject') {
      // Let Claude stop — empty response means no blocking
      stopResult = {}
    } else {
      // edit = block stop + supply user instructions
      stopResult = { decision: 'block', reason: body.reason || 'Continue with modifications.' }
    }

    pending.resolve(stopResult)

    return jsonResponse({ success: true, requestId: body.requestId, decision: body.decision })
  } catch (e) {
    return errorResponse('Invalid JSON body', 400)
  }
}

// ── Ignore (remove from UI state only, don't respond to Claude Code) ──

export async function handleHooksApprovalIgnore(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json() as { requestId: string }

    if (!body.requestId) {
      return errorResponse('Missing requestId', 400)
    }

    console.log(`[HooksApproval] Ignoring ${body.requestId} (removing from UI state only)`)

    // Remove from session state → broadcasts patch to all clients
    // The long-poll promise stays alive and will timeout naturally (120s),
    // at which point Claude Code gets {} and handles it with default behavior.
    notifyResolveApproval(body.requestId, 'ignore')

    return jsonResponse({ success: true, requestId: body.requestId })
  } catch (e) {
    return errorResponse('Invalid JSON body', 400)
  }
}

// ── List pending (for state recovery) ──

export async function handleHooksApprovalList(req: Request): Promise<Response | null> {
  if (req.method !== 'GET') return null

  const permissions: unknown[] = []
  const plans: unknown[] = []

  for (const [id, p] of pendingRequests) {
    const base = {
      requestId: id,
      session_id: p.payload.session_id,
      createdAt: p.createdAt
    }

    if (p.type === 'permission') {
      permissions.push({
        ...base,
        tool_name: p.payload.tool_name,
        tool_input: p.payload.tool_input,
        agent_name: p.payload.agent_name
      })
    } else {
      plans.push({
        ...base,
        permission_mode: p.payload.permission_mode,
        lastAssistantText: '' // Not stored, UI gets it from WS broadcast
      })
    }
  }

  return jsonResponse({ permissions, plans })
}
