import { NextResponse } from "next/server"
import { createClient } from "@/lib/supabase/server"
import { getResourceLocationPathLabel } from "@/lib/resources/location"

type ChatMessage = {
  role: "user" | "assistant"
  content: string
}

type AvailabilityAction = {
  type: "check_availability"
  params?: {
    resourceTypeSlug?: string
    resourceId?: string
    startAt?: string
    endAt?: string
  }
}

type CreateAction = {
  type: "create_reservation"
  params?: {
    resourceId?: string
    startAt?: string
    endAt?: string
    notes?: string | null
  }
}

type EditAction = {
  type: "edit_reservation"
  params?: {
    reservationId?: string
    resourceId?: string
    startAt?: string
    endAt?: string
    notes?: string | null
  }
}

type CancelAction = {
  type: "cancel_reservation"
  params?: {
    reservationId?: string
  }
}

type SammyAction = AvailabilityAction | CreateAction | EditAction | CancelAction | { type: "none" }

type PendingAction = Exclude<SammyAction, AvailabilityAction | { type: "none" }> & {
  summary: string
}

type SammyResponseShape = {
  reply: string
  action?: SammyAction
}

type AvailabilityResult = {
  id: string
  name: string
  resourceTypeName: string | null
  resourceTypeSlug: string | null
  location: string | null
  floor: string | null
  capacity: number | null
  amenities: string[]
  imageUrl: string | null
  startAt: string | null
  endAt: string | null
}

type RequestBody = {
  messages?: ChatMessage[]
  pendingAction?: PendingAction | null
  confirmPendingAction?: boolean
  clientNowIso?: string
  clientUtcOffsetMinutes?: number
}

const DEFAULT_CLAUDE_MODELS = ["claude-sonnet-4-6", "claude-opus-4-6", "claude-opus-4-5"] as const

function getAnthropicApiKey() {
  const key = process.env.ANTHROPIC_API_KEY
  if (typeof key === "string" && key.trim().length > 0) return key
  throw new Error("Falta configurar ANTHROPIC_API_KEY")
}

function getClaudeModelList() {
  const configured = process.env.SAMMY_CLAUDE_MODELS
  if (!configured) return [...DEFAULT_CLAUDE_MODELS]
  const parsed = configured
    .split(",")
    .map((value) => value.trim())
    .filter((value) => value.startsWith("claude-"))
  return parsed.length > 0 ? parsed : [...DEFAULT_CLAUDE_MODELS]
}

function toIsoIfValid(value: string | undefined) {
  if (!value) return null
  const asDate = new Date(value)
  return Number.isNaN(asDate.getTime()) ? null : asDate.toISOString()
}

function normalizeAction(action: SammyAction | undefined): SammyAction {
  if (!action || typeof action !== "object" || !("type" in action)) return { type: "none" }
  switch (action.type) {
    case "check_availability":
    case "create_reservation":
    case "edit_reservation":
    case "cancel_reservation":
      return action
    default:
      return { type: "none" }
  }
}

function sanitizeJsonResponse(raw: string) {
  const trimmed = raw.trim()
  if (trimmed.startsWith("```")) {
    const normalized = trimmed.replace(/^```(?:json)?/i, "").replace(/```$/i, "").trim()
    return normalized
  }
  return trimmed
}

function humanizeMinutes(minutes: number | undefined) {
  if (!minutes || !Number.isFinite(minutes) || minutes <= 0) return null
  if (minutes % (24 * 60) === 0) {
    const days = minutes / (24 * 60)
    return `${days} día${days === 1 ? "" : "s"}`
  }
  if (minutes % 60 === 0) {
    const hours = minutes / 60
    return `${hours} hora${hours === 1 ? "" : "s"}`
  }
  return `${minutes} minutos`
}

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object") return null
  return value as Record<string, unknown>
}

function getImageUrlFromMetadata(metadata: unknown) {
  const record = asRecord(metadata)
  return typeof record?.image_url === "string" && record.image_url.trim().length > 0
    ? record.image_url.trim()
    : null
}

function getStringArray(value: unknown) {
  if (!Array.isArray(value)) return []
  return value.filter((item): item is string => typeof item === "string")
}

function getResourceTypeFields(value: unknown) {
  const record = asRecord(value)
  const name = typeof record?.name === "string" ? record.name : null
  const slug = typeof record?.slug === "string" ? record.slug : null
  return { name, slug }
}

function normalizeText(value: string) {
  return value
    .trim()
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
}

function parseTimeRangeFromText(value: string) {
  const text = normalizeText(value)
  const match = text.match(/(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*(?:-|a|hasta)\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/)
  if (!match) return null

  const startHourRaw = Number(match[1])
  const startMinute = Number(match[2] ?? "0")
  const startMeridiem = match[3] as "am" | "pm" | undefined
  const endHourRaw = Number(match[4])
  const endMinute = Number(match[5] ?? "0")
  const endMeridiem = match[6] as "am" | "pm" | undefined

  if (
    Number.isNaN(startHourRaw) ||
    Number.isNaN(startMinute) ||
    Number.isNaN(endHourRaw) ||
    Number.isNaN(endMinute)
  ) {
    return null
  }
  if (
    startHourRaw < 0 ||
    startHourRaw > 24 ||
    endHourRaw < 0 ||
    endHourRaw > 24 ||
    startMinute < 0 ||
    startMinute > 59 ||
    endMinute < 0 ||
    endMinute > 59
  ) {
    return null
  }

  const inferredMeridiem = endMeridiem ?? startMeridiem
  const applyMeridiem = (hour: number, meridiem: "am" | "pm" | undefined) => {
    if (!meridiem) return hour
    if (hour === 12) return meridiem === "am" ? 0 : 12
    return meridiem === "pm" ? hour + 12 : hour
  }

  const startHour = applyMeridiem(startHourRaw, startMeridiem ?? inferredMeridiem)
  const endHour = applyMeridiem(endHourRaw, endMeridiem ?? inferredMeridiem)
  return {
    startHour,
    startMinute,
    endHour,
    endMinute,
  }
}

function hasExplicitTimeSignal(value: string) {
  if (!value || value.trim().length === 0) return false
  if (parseTimeRangeFromText(value)) return true
  if (/\b(startat|endat)\s*=\s*\d{4}-\d{2}-\d{2}t\d{2}:\d{2}/i.test(value)) return true
  if (/\b\d{4}-\d{2}-\d{2}t\d{2}:\d{2}(?::\d{2})?(?:\.\d{1,3})?z?\b/i.test(value)) return true
  return false
}

function hasValidIsoRange(startAt: string | null, endAt: string | null) {
  if (!startAt || !endAt) return false
  const start = new Date(startAt)
  const end = new Date(endAt)
  if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return false
  return end > start
}

function redactIdsFromInformationalReply(value: string) {
  if (!value || value.trim().length === 0) return value
  const withoutIds = value
    .replace(/\(\s*(?:resource|reservation)?id\s*:\s*[\w-]+\s*\)/gi, "")
    .replace(/\b(?:resource|reservation)?id\s*[:=]\s*[\w-]+\b/gi, "")
    .replace(/\bID\s*[:=]\s*[\w-]+\b/g, "")

  const normalized = withoutIds
    .split("\n")
    .map((line) => {
      const compact = line
        .replace(/\s{2,}/g, " ")
        .replace(/\s+([,.;:!?])/g, "$1")
        .trimEnd()
      return compact === "-" ? "" : compact
    })
    .join("\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim()

  return normalized.length > 0 ? normalized : value
}

function getLastUserMessage(messages: ChatMessage[]) {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const message = messages[index]
    if (message.role === "user") return message
  }
  return null
}

type LocalDateParts = {
  year: number
  month: number
  day: number
}

function getClientNow(body: RequestBody) {
  const parsed = typeof body.clientNowIso === "string" ? new Date(body.clientNowIso) : null
  if (parsed && !Number.isNaN(parsed.getTime())) return parsed
  return new Date()
}

function getSafeClientOffsetMinutes(body: RequestBody) {
  if (typeof body.clientUtcOffsetMinutes !== "number" || !Number.isFinite(body.clientUtcOffsetMinutes)) {
    return null
  }
  const rounded = Math.trunc(body.clientUtcOffsetMinutes)
  if (rounded < -14 * 60 || rounded > 14 * 60) return null
  return rounded
}

function getLocalDatePartsFromClientContext(body: RequestBody): LocalDateParts | null {
  const offsetMinutes = getSafeClientOffsetMinutes(body)
  if (offsetMinutes === null) return null

  const now = getClientNow(body)
  // Rebase "client local time" into UTC getters so it is independent from server timezone.
  const pseudoLocal = new Date(now.getTime() - offsetMinutes * 60 * 1000)
  return {
    year: pseudoLocal.getUTCFullYear(),
    month: pseudoLocal.getUTCMonth(),
    day: pseudoLocal.getUTCDate(),
  }
}

function addDays(parts: LocalDateParts, days: number): LocalDateParts {
  const base = new Date(Date.UTC(parts.year, parts.month, parts.day))
  base.setUTCDate(base.getUTCDate() + days)
  return {
    year: base.getUTCFullYear(),
    month: base.getUTCMonth(),
    day: base.getUTCDate(),
  }
}

function toIsoFromLocalDateTime(parts: LocalDateParts, hour: number, minute: number, offsetMinutes: number) {
  const utcTimestamp = Date.UTC(parts.year, parts.month, parts.day, hour, minute, 0, 0) + offsetMinutes * 60 * 1000
  return new Date(utcTimestamp).toISOString()
}

function resolveReferenceDateFromMessages(messages: ChatMessage[], body: RequestBody) {
  const localDateParts = getLocalDatePartsFromClientContext(body)
  const now = getClientNow(body)
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const message = messages[index]
    if (message.role !== "user") continue
    const text = normalizeText(message.content)
    if (text.includes("manana")) {
      if (localDateParts) return addDays(localDateParts, 1)
      const target = new Date(now)
      target.setDate(target.getDate() + 1)
      return {
        year: target.getFullYear(),
        month: target.getMonth(),
        day: target.getDate(),
      }
    }
    if (text.includes("hoy")) {
      if (localDateParts) return localDateParts
      return {
        year: now.getFullYear(),
        month: now.getMonth(),
        day: now.getDate(),
      }
    }
  }
  return null
}

function getIsoWindowFromConversation(messages: ChatMessage[], body: RequestBody) {
  const referenceDate = resolveReferenceDateFromMessages(messages, body)
  if (!referenceDate) return null

  const clientOffset = getSafeClientOffsetMinutes(body)
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const message = messages[index]
    if (message.role !== "user") continue
    const range = parseTimeRangeFromText(message.content)
    if (!range) continue

    const startIso =
      clientOffset !== null
        ? toIsoFromLocalDateTime(referenceDate, range.startHour, range.startMinute, clientOffset)
        : new Date(referenceDate.year, referenceDate.month, referenceDate.day, range.startHour, range.startMinute, 0, 0).toISOString()
    const endIso =
      clientOffset !== null
        ? toIsoFromLocalDateTime(referenceDate, range.endHour, range.endMinute, clientOffset)
        : new Date(referenceDate.year, referenceDate.month, referenceDate.day, range.endHour, range.endMinute, 0, 0).toISOString()

    const start = new Date(startIso)
    const end = new Date(endIso)
    if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || end <= start) return null
    return {
      startAt: startIso,
      endAt: endIso,
    }
  }
  return null
}

type ContextResource = {
  id: string
  name: string
  resourceTypeName: string | null
  resourceTypeSlug: string | null
  floor: string | null
  location: string | null
}

const RESOURCE_STOP_WORDS = new Set(["de", "del", "la", "el", "los", "las", "y", "en", "con", "un", "una"])

function scoreResourceMatch(input: string, resource: ContextResource) {
  const normalizedInput = normalizeText(input)
  const normalizedName = normalizeText(resource.name)
  if (normalizedInput.includes(normalizedName)) return 100

  const tokens = normalizedName
    .split(/\s+/)
    .map((token) => token.replace(/[^a-z0-9]/g, ""))
    .filter((token) => token.length > 2 && !RESOURCE_STOP_WORDS.has(token))

  let score = 0
  for (const token of tokens) {
    if (normalizedInput.includes(token)) score += 1
  }
  return score
}

function pickResourceByConversation(messages: ChatMessage[], resources: ContextResource[]) {
  const userMessages = messages.filter((message) => message.role === "user")
  for (let index = userMessages.length - 1; index >= 0; index -= 1) {
    const userMessage = userMessages[index]
    const text = normalizeText(userMessage.content)
    const deicticReference = /\b(ese|esa|ese horario|esa cancha|ese recurso|la misma)\b/.test(text)

    const ranked = resources
      .map((resource) => ({ resource, score: scoreResourceMatch(userMessage.content, resource) }))
      .filter((entry) => entry.score > 0)
      .sort((a, b) => b.score - a.score)

    if (ranked.length > 0 && (ranked.length === 1 || ranked[0].score > ranked[1].score)) {
      return ranked[0].resource
    }

    if (deicticReference) {
      for (let assistantIndex = messages.length - 1; assistantIndex >= 0; assistantIndex -= 1) {
        const assistantMessage = messages[assistantIndex]
        if (assistantMessage.role !== "assistant") continue
        const bulletLines = assistantMessage.content
          .split("\n")
          .map((line) => line.trim())
          .filter((line) => line.startsWith("- "))

        const matched = bulletLines
          .map((line) => line.slice(2).trim())
          .map((label) => resources.find((resource) => normalizeText(label).includes(normalizeText(resource.name))))
          .filter((resource): resource is ContextResource => Boolean(resource))

        if (matched.length === 1) return matched[0]
      }
    }
  }
  return null
}

function enrichAvailabilityActionFromConversation(
  action: AvailabilityAction,
  messages: ChatMessage[],
  resources: ContextResource[],
  body: RequestBody,
  options?: {
    allowWindowInference?: boolean
  },
): AvailabilityAction {
  const allowWindowInference = options?.allowWindowInference ?? true
  const startAt = toIsoIfValid(action.params?.startAt)
  const endAt = toIsoIfValid(action.params?.endAt)
  const resourceId = action.params?.resourceId?.trim() ?? null

  const inferredWindow = allowWindowInference && (!startAt || !endAt) ? getIsoWindowFromConversation(messages, body) : null
  const inferredResource = !resourceId ? pickResourceByConversation(messages, resources) : null

  return {
    ...action,
    params: {
      ...action.params,
      resourceId: resourceId ?? inferredResource?.id,
      resourceTypeSlug: action.params?.resourceTypeSlug ?? inferredResource?.resourceTypeSlug ?? undefined,
      startAt: startAt ?? inferredWindow?.startAt,
      endAt: endAt ?? inferredWindow?.endAt,
    },
  }
}

function enrichMutationActionFromConversation(
  action: CreateAction | EditAction,
  messages: ChatMessage[],
  resources: ContextResource[],
  body: RequestBody,
): CreateAction | EditAction {
  const startAt = toIsoIfValid(action.params?.startAt)
  const endAt = toIsoIfValid(action.params?.endAt)
  const resourceId = action.params?.resourceId?.trim() ?? null

  const inferredWindow = !startAt || !endAt ? getIsoWindowFromConversation(messages, body) : null
  const inferredResource = !resourceId ? pickResourceByConversation(messages, resources) : null

  return {
    ...action,
    params: {
      ...action.params,
      resourceId: resourceId ?? inferredResource?.id,
      startAt: startAt ?? inferredWindow?.startAt,
      endAt: endAt ?? inferredWindow?.endAt,
    },
  }
}

async function callClaudeWithFallback({
  systemPrompt,
  messages,
}: {
  systemPrompt: string
  messages: ChatMessage[]
}) {
  const apiKey = getAnthropicApiKey()
  const models = getClaudeModelList()
  const serializedConversation = messages
    .slice(-10)
    .map((message) => `${message.role.toUpperCase()}: ${message.content}`)
    .join("\n\n")

  const userPrompt = [
    "Conversacion reciente:",
    serializedConversation || "(sin mensajes previos)",
    "",
    "Responde solo JSON valido con shape:",
    '{ "reply": "texto para usuario", "action": { "type": "none|check_availability|create_reservation|edit_reservation|cancel_reservation", "params": {} } }',
  ].join("\n")

  const errors: string[] = []

  for (const model of models) {
    const response = await fetch("https://api.anthropic.com/v1/messages", {
      method: "POST",
      headers: {
        "x-api-key": apiKey,
        "anthropic-version": "2023-06-01",
        "content-type": "application/json",
      },
      body: JSON.stringify({
        model,
        max_tokens: 700,
        temperature: 0.2,
        system: systemPrompt,
        messages: [{ role: "user", content: userPrompt }],
      }),
    })

    if (!response.ok) {
      const payload = (await response.text().catch(() => "")) || `status ${response.status}`
      errors.push(`${model}: ${payload.slice(0, 240)}`)
      continue
    }

    const payload = (await response.json().catch(() => null)) as
      | { content?: Array<{ type?: string; text?: string }> }
      | null
    const text = payload?.content?.find((block) => block.type === "text")?.text
    if (!text) {
      errors.push(`${model}: empty text response`)
      continue
    }

    const parsed = JSON.parse(sanitizeJsonResponse(text)) as SammyResponseShape
    return { parsed, model }
  }

  throw new Error(`Falló la solicitud a Claude en todos los modelos configurados (${errors.join(" | ")})`)
}

async function buildContext(supabase: Awaited<ReturnType<typeof createClient>>, userId: string) {
  const [resourcesRes, reservationsRes] = await Promise.all([
    supabase
      .from("resources")
      .select("id, name, floor, metadata, resource_types(name, slug)")
      .eq("is_active", true)
      .order("name")
      .limit(80),
    supabase
      .from("reservations")
      .select("id, start_at, end_at, status, notes, resource_id, resources(name, resource_types(name, slug))")
      .eq("user_id", userId)
      .order("start_at", { ascending: true })
      .limit(25),
  ])

  const resources = (resourcesRes.data ?? []).map((resource) => {
    const resourceType = getResourceTypeFields((resource as { resource_types?: unknown }).resource_types)
    return {
      id: resource.id,
      name: resource.name,
      resourceTypeName: resourceType.name,
      resourceTypeSlug: resourceType.slug,
      floor: resource.floor,
      location: getResourceLocationPathLabel(resource),
    }
  })

  const myReservations = (reservationsRes.data ?? []).map((reservation) => {
    const reservationResource = asRecord((reservation as { resources?: unknown }).resources)
    const resourceType = getResourceTypeFields(reservationResource?.resource_types)
    return {
      id: reservation.id,
      resourceId: reservation.resource_id,
      resourceName: typeof reservationResource?.name === "string" ? reservationResource.name : "Recurso",
      resourceTypeSlug: resourceType.slug,
      startAt: reservation.start_at,
      endAt: reservation.end_at,
      status: reservation.status,
      notes: reservation.notes,
    }
  })

  return { resources, myReservations }
}

async function executeAvailabilityAction(
  supabase: Awaited<ReturnType<typeof createClient>>,
  action: AvailabilityAction,
) {
  const startAtIso = toIsoIfValid(action.params?.startAt)
  const endAtIso = toIsoIfValid(action.params?.endAt)

  const resourcesQuery = supabase
    .from("resources")
    .select("id, name, floor, capacity, amenities, metadata, resource_types(name, slug)")
    .eq("is_active", true)
    .order("name")
    .limit(120)

  const { data: resources } = await resourcesQuery
  let typedResources = (resources ?? []) as Array<{
    id: string
    name: string
    floor: string | null
    capacity: number | null
    amenities: unknown
    metadata: unknown
    resource_types: { name: string; slug: string } | null
  }>

  const toAvailabilityResult = (
    resource: {
      id: string
      name: string
      floor: string | null
      capacity: number | null
      amenities: unknown
      metadata: unknown
      resource_types: { name: string; slug: string } | null
    },
    range: { startAt: string | null; endAt: string | null },
  ): AvailabilityResult => ({
    id: resource.id,
    name: resource.name,
    resourceTypeName: resource.resource_types?.name ?? null,
    resourceTypeSlug: resource.resource_types?.slug ?? null,
    location: getResourceLocationPathLabel(resource),
    floor: resource.floor,
    capacity: resource.capacity,
    amenities: getStringArray(resource.amenities),
    imageUrl: getImageUrlFromMetadata(resource.metadata),
    startAt: range.startAt,
    endAt: range.endAt,
  })

  const slugFilter = action.params?.resourceTypeSlug?.trim().toLowerCase()
  if (slugFilter) {
    typedResources = typedResources.filter((resource) => resource.resource_types?.slug === slugFilter)
  }
  const resourceIdFilter = action.params?.resourceId?.trim()
  if (resourceIdFilter) {
    typedResources = typedResources.filter((resource) => resource.id === resourceIdFilter)
  }

  if (!startAtIso || !endAtIso) {
    const top = typedResources.slice(0, 6)
    if (top.length === 0) {
      return {
        summary: "No encontre recursos activos para ese filtro.",
        items: [] as AvailabilityResult[],
      }
    }
    return {
      summary:
        "Necesito horario exacto para validar disponibilidad real. Te muestro cards con recursos activos para que elijas y me pases la franja.",
      items: top.map((resource) => toAvailabilityResult(resource, { startAt: null, endAt: null })),
    }
  }

  const { data: conflicts } = await supabase
    .from("reservations")
    .select("resource_id")
    .in("status", ["confirmed", "checked_in"])
    .lt("start_at", endAtIso)
    .gt("end_at", startAtIso)

  const busy = new Set((conflicts ?? []).map((item) => item.resource_id))
  const available = typedResources.filter((resource) => !busy.has(resource.id))
  const first = available.slice(0, 6)

  if (first.length === 0) {
    return {
      summary: "No encontre disponibilidad para ese horario.",
      items: [] as AvailabilityResult[],
    }
  }

  return {
    summary: `Encontre ${available.length} recurso(s) disponibles para ese horario.`,
    items: first.map((resource) =>
      toAvailabilityResult(resource, {
        startAt: startAtIso,
        endAt: endAtIso,
      }),
    ),
  }
}

function buildInternalHeaders(request: Request) {
  return {
    "content-type": "application/json",
    cookie: request.headers.get("cookie") ?? "",
  }
}

async function executePendingAction(request: Request, pendingAction: PendingAction) {
  switch (pendingAction.type) {
    case "create_reservation": {
      const resourceId = pendingAction.params?.resourceId?.trim()
      const startAt = toIsoIfValid(pendingAction.params?.startAt)
      const endAt = toIsoIfValid(pendingAction.params?.endAt)
      if (!resourceId || !startAt || !endAt) {
        return "No pude crear la reserva: faltan datos obligatorios (resourceId/startAt/endAt)."
      }

      const response = await fetch(new URL("/api/reservations", request.url), {
        method: "POST",
        headers: buildInternalHeaders(request),
        body: JSON.stringify({
          resourceId,
          startAt,
          endAt,
          notes: pendingAction.params?.notes ?? null,
        }),
        cache: "no-store",
      })
      const payload = (await response.json().catch(() => null)) as
        | { error?: string; reservation?: { id?: string } }
        | null
      if (!response.ok) return `No pude crear la reserva: ${payload?.error ?? "error desconocido"}`
      return `Reserva creada correctamente${payload?.reservation?.id ? ` (id: ${payload.reservation.id})` : ""}.`
    }
    case "edit_reservation": {
      const reservationId = pendingAction.params?.reservationId?.trim()
      const resourceId = pendingAction.params?.resourceId?.trim()
      const startAt = toIsoIfValid(pendingAction.params?.startAt)
      const endAt = toIsoIfValid(pendingAction.params?.endAt)
      if (!reservationId || !resourceId || !startAt || !endAt) {
        return "No pude editar la reserva: faltan datos obligatorios (reservationId/resourceId/startAt/endAt)."
      }

      const response = await fetch(new URL(`/api/reservations/${encodeURIComponent(reservationId)}`, request.url), {
        method: "PATCH",
        headers: buildInternalHeaders(request),
        body: JSON.stringify({
          resourceId,
          startAt,
          endAt,
          notes: pendingAction.params?.notes ?? null,
        }),
        cache: "no-store",
      })
      const payload = (await response.json().catch(() => null)) as { error?: string } | null
      if (!response.ok) return `No pude editar la reserva: ${payload?.error ?? "error desconocido"}`
      return `Reserva editada correctamente (id: ${reservationId}).`
    }
    case "cancel_reservation": {
      const reservationId = pendingAction.params?.reservationId?.trim()
      if (!reservationId) {
        return "No pude cancelar la reserva: falta reservationId."
      }

      const response = await fetch(
        new URL(`/api/reservations/${encodeURIComponent(reservationId)}/cancel`, request.url),
        {
          method: "POST",
          headers: {
            cookie: request.headers.get("cookie") ?? "",
          },
          cache: "no-store",
        },
      )
      const payload = (await response.json().catch(() => null)) as
        | {
            error?: string
            errorCode?: string
            cancellationDeadlineMinutes?: number
            policyScope?: "resource" | "resource_type" | "default"
          }
        | null
      if (!response.ok) {
        if (payload?.errorCode === "CANCELLATION_POLICY_WINDOW_CLOSED") {
          if (payload?.error) return payload.error
          const readable = humanizeMinutes(payload.cancellationDeadlineMinutes)
          return readable
            ? `No pude cancelar la reserva por la política de cancelación de esta reserva (límite: ${readable} antes del inicio).`
            : "No pude cancelar la reserva por la política de cancelación de esta reserva."
        }
        return `No pude cancelar la reserva: ${payload?.error ?? "error desconocido"}`
      }
      return `Reserva cancelada correctamente (id: ${reservationId}).`
    }
    default:
      return "No hay accion pendiente para ejecutar."
  }
}

function buildMutationSummary(action: Exclude<SammyAction, AvailabilityAction | { type: "none" }>) {
  switch (action.type) {
    case "create_reservation":
      return "Crear una reserva nueva"
    case "edit_reservation":
      return "Editar una reserva existente"
    case "cancel_reservation":
      return "Cancelar una reserva"
    default:
      return "Accion pendiente"
  }
}

export async function POST(request: Request) {
  const body = (await request.json().catch(() => ({}))) as RequestBody
  const messages = (body.messages ?? []).filter(
    (message): message is ChatMessage =>
      message.role !== undefined &&
      (message.role === "user" || message.role === "assistant") &&
      typeof message.content === "string" &&
      message.content.trim().length > 0,
  )

  const supabase = await createClient()
  const {
    data: { user },
    error: userError,
  } = await supabase.auth.getUser()

  if (userError || !user) {
    return NextResponse.json({ error: "No autorizado" }, { status: 401 })
  }

  if (body.confirmPendingAction && body.pendingAction) {
    const executionMessage = await executePendingAction(request, body.pendingAction)
    return NextResponse.json({
      assistantMessage: executionMessage,
      pendingAction: null,
      availabilityResults: null,
      executed: true,
    })
  }

  const context = await buildContext(supabase, user.id)
  const systemPrompt = [
    "Eres Sammy, asistente de reservas de HU Rooms.",
    "Habla siempre en espanol claro y breve.",
    "Nunca inventes disponibilidad ni IDs.",
    "No pidas nombre de usuario: siempre operas para la persona autenticada.",
    "Politica de ejecucion:",
    "- check_availability se ejecuta automaticamente.",
    "- create_reservation/edit_reservation/cancel_reservation requieren confirmacion explicita del usuario.",
    "Para mutaciones, el campo reply debe ser una pregunta de confirmacion al usuario (todavia no ejecutada).",
    "No asumas rangos horarios por defecto (por ejemplo 24h) si el usuario no indico una hora.",
    "Si no hay horario explicito para crear/editar, responde con action.type='none' y pide hora de inicio y fin.",
    "Cuando haya resultados de disponibilidad, prioriza texto breve porque la UI muestra cards con los detalles.",
    "Cuando el usuario da horario, siempre incluye startAt/endAt en ISO 8601 en action.params.",
    "Cuando el usuario referencia un recurso (por nombre o por 'ese/esa'), resuelve e incluye resourceId en action.params.",
    "Solo muestra IDs cuando reportes resultado de una mutacion ejecutada (crear/editar/cancelar). En busquedas o disponibilidad, nunca muestres IDs.",
    "Si faltan datos para una accion, devuelve action.type='none' y pide lo minimo necesario.",
    "",
    "Contexto operativo (JSON):",
    JSON.stringify(
      {
        now: new Date().toISOString(),
        resources: context.resources,
        myReservations: context.myReservations,
      },
      null,
      2,
    ),
  ].join("\n")

  let aiResult: SammyResponseShape
  let modelUsed: string | null = null
  try {
    const response = await callClaudeWithFallback({ systemPrompt, messages })
    aiResult = response.parsed
    modelUsed = response.model
  } catch {
    return NextResponse.json(
      {
        assistantMessage:
          "No pude procesar tu pedido con IA en este momento. Intenta de nuevo en unos segundos.",
        pendingAction: null,
        error: "No se pudo procesar la solicitud con IA",
      },
      { status: 500 },
    )
  }

  let action = normalizeAction(aiResult.action)
  if (action.type === "create_reservation" || action.type === "edit_reservation") {
    action = enrichMutationActionFromConversation(action, messages, context.resources, body)
  }

  if (action.type === "create_reservation" || action.type === "edit_reservation") {
    const lastUserMessage = getLastUserMessage(messages)
    const startAt = toIsoIfValid(action.params?.startAt)
    const endAt = toIsoIfValid(action.params?.endAt)
    const hasValidRange = hasValidIsoRange(startAt, endAt)
    const hasExplicitTimeInLastMessage = hasExplicitTimeSignal(lastUserMessage?.content ?? "")
    if (!hasExplicitTimeInLastMessage && !hasValidRange) {
      return NextResponse.json({
        assistantMessage:
          "Antes de preparar esa accion necesito horario explicito (inicio y fin). Decime, por ejemplo: hoy de 15:00 a 16:00.",
        availabilityResults: null,
        pendingAction: null,
        modelUsed,
      })
    }

    const resourceId = action.params?.resourceId?.trim()
    if (!resourceId || !hasValidRange) {
      return NextResponse.json({
        assistantMessage:
          "Todavia faltan datos explicitos para continuar: recurso, hora de inicio y hora de fin. Pasamelos y te preparo la confirmacion.",
        availabilityResults: null,
        pendingAction: null,
        modelUsed,
      })
    }

    if (action.type === "edit_reservation") {
      const reservationId = action.params?.reservationId?.trim()
      if (!reservationId) {
        return NextResponse.json({
          assistantMessage:
            "Para editar necesito que me indiques explicitamente cual reserva queres modificar (reservationId o referencia inequívoca).",
          availabilityResults: null,
          pendingAction: null,
          modelUsed,
        })
      }
    }
  }

  if (action.type === "check_availability") {
    const lastUserMessage = getLastUserMessage(messages)
    const lastUserHasExplicitTime = hasExplicitTimeSignal(lastUserMessage?.content ?? "")
    const normalizedAvailabilityAction: AvailabilityAction = lastUserHasExplicitTime
      ? action
      : {
          ...action,
          params: {
            ...action.params,
            startAt: undefined,
            endAt: undefined,
          },
        }

    const enrichedAction = enrichAvailabilityActionFromConversation(
      normalizedAvailabilityAction,
      messages,
      context.resources,
      body,
      {
        allowWindowInference: lastUserHasExplicitTime,
      },
    )
    const availability = await executeAvailabilityAction(supabase, enrichedAction)
    const shouldPrioritizeCards = availability.items.length > 0
    const availabilityReply = shouldPrioritizeCards ? aiResult.reply : `${aiResult.reply}\n\n${availability.summary}`
    return NextResponse.json({
      assistantMessage: redactIdsFromInformationalReply(availabilityReply),
      availabilityResults: availability.items,
      pendingAction: null,
      modelUsed,
    })
  }

  if (action.type === "create_reservation" || action.type === "edit_reservation" || action.type === "cancel_reservation") {
    const pendingAction: PendingAction = {
      ...action,
      summary: buildMutationSummary(action),
    }
    return NextResponse.json({
      assistantMessage: aiResult.reply,
      availabilityResults: null,
      pendingAction,
      modelUsed,
    })
  }

  return NextResponse.json({
    assistantMessage: redactIdsFromInformationalReply(aiResult.reply),
    availabilityResults: null,
    pendingAction: null,
    modelUsed,
  })
}
