import { NextResponse } from "next/server"
import { createClient } from "@supabase/supabase-js"
import type { Database } from "@/lib/supabase/database.types"
import { isNoShowDue, resolveCheckInWindowMinutes } from "@/lib/reservations/policies"
import { getSupabaseAdminEnv } from "@/lib/supabase/env.server"

// NOTE: Vercel Hobby only supports cron jobs that run once per day.
// Keep `vercel.json` schedule daily unless the project is upgraded.
function getAdminClient() {
  const { url: supabaseUrl, serviceRoleKey } = getSupabaseAdminEnv()

  return createClient<Database>(supabaseUrl, serviceRoleKey, {
    auth: { autoRefreshToken: false, persistSession: false },
  })
}

function isAuthorizedRequest(request: Request) {
  const cronSecret = process.env.NO_SHOW_CRON_SECRET ?? process.env.CRON_SECRET
  if (!cronSecret) {
    return {
      ok: false,
      status: 500,
      message: "Falta configurar NO_SHOW_CRON_SECRET o CRON_SECRET",
    }
  }

  const authHeader = request.headers.get("authorization")
  if (authHeader !== `Bearer ${cronSecret}`) {
    return {
      ok: false,
      status: 401,
      message: "No autorizado",
    }
  }

  return { ok: true, status: 200, message: "ok" }
}

async function runNoShowDetection(request: Request) {
  const authResult = isAuthorizedRequest(request)
  if (!authResult.ok) {
    return NextResponse.json({ error: authResult.message }, { status: authResult.status })
  }

  let admin: ReturnType<typeof getAdminClient>
  try {
    admin = getAdminClient()
  } catch (error) {
    const message = error instanceof Error ? error.message : "No se pudo crear el cliente admin"
    return NextResponse.json({ error: message }, { status: 500 })
  }

  const now = new Date()
  const nowIso = now.toISOString()

  const { data: candidates, error: candidatesError } = await admin
    .from("reservations")
    .select("id, user_id, resource_id, start_at, checked_in_at, status")
    .eq("status", "confirmed")
    .is("checked_in_at", null)
    .lte("start_at", nowIso)

  if (candidatesError) {
    return NextResponse.json({ error: "No se pudieron consultar las reservas candidatas a no-show" }, { status: 500 })
  }

  if (!candidates || candidates.length === 0) {
    return NextResponse.json({
      ok: true,
      candidates: 0,
      markedNoShow: 0,
      createdEvents: 0,
    })
  }

  const resourceIds = [...new Set(candidates.map((reservation) => reservation.resource_id))]
  const { data: resources, error: resourcesError } = await admin
    .from("resources")
    .select("id, resource_type_id")
    .in("id", resourceIds)

  if (resourcesError) {
    return NextResponse.json({ error: "No se pudieron consultar los recursos asociados" }, { status: 500 })
  }

  const resourceTypeByResourceId = new Map(
    (resources ?? []).map((resource) => [resource.id, resource.resource_type_id]),
  )

  const { data: policies, error: policiesError } = await admin
    .from("resource_policies")
    .select("*")

  if (policiesError) {
    return NextResponse.json({ error: "No se pudieron consultar las políticas de no-show" }, { status: 500 })
  }

  const noShowReservationIds = candidates
    .filter((reservation) => {
      const resourceTypeId = resourceTypeByResourceId.get(reservation.resource_id) ?? null
      const checkInWindowMinutes = resolveCheckInWindowMinutes({
        resourceId: reservation.resource_id,
        resourceTypeId,
        policies: policies ?? [],
      })

      return isNoShowDue({
        startAt: reservation.start_at,
        now,
        windowMinutes: checkInWindowMinutes,
      })
    })
    .map((reservation) => reservation.id)

  if (noShowReservationIds.length === 0) {
    return NextResponse.json({
      ok: true,
      candidates: candidates.length,
      markedNoShow: 0,
      createdEvents: 0,
    })
  }

  const { data: updatedReservations, error: updateError } = await admin
    .from("reservations")
    .update({ status: "no_show" })
    .in("id", noShowReservationIds)
    .eq("status", "confirmed")
    .is("checked_in_at", null)
    .select("id, user_id")

  if (updateError) {
    return NextResponse.json({ error: "No se pudieron marcar reservas como no-show" }, { status: 500 })
  }

  if (!updatedReservations || updatedReservations.length === 0) {
    return NextResponse.json({
      ok: true,
      candidates: candidates.length,
      markedNoShow: 0,
      createdEvents: 0,
    })
  }

  const updatedReservationIds = updatedReservations.map((reservation) => reservation.id)
  const { data: existingEvents, error: existingEventsError } = await admin
    .from("no_show_events")
    .select("reservation_id")
    .in("reservation_id", updatedReservationIds)

  if (existingEventsError) {
    return NextResponse.json({ error: "No se pudieron consultar eventos de no-show existentes" }, { status: 500 })
  }

  const existingEventReservationIds = new Set(
    (existingEvents ?? []).map((event) => event.reservation_id),
  )
  const eventsToInsert = updatedReservations
    .filter((reservation) => !existingEventReservationIds.has(reservation.id))
    .map((reservation) => ({
      reservation_id: reservation.id,
      user_id: reservation.user_id,
      notes: "Generado por el cron de no-show",
    }))

  if (eventsToInsert.length > 0) {
    const { error: insertEventsError } = await admin.from("no_show_events").insert(eventsToInsert)
    if (insertEventsError) {
      return NextResponse.json(
        {
          error: "No se pudieron crear eventos de no-show",
          markedNoShow: updatedReservations.length,
          createdEvents: 0,
        },
        { status: 500 },
      )
    }
  }

  return NextResponse.json({
    ok: true,
    candidates: candidates.length,
    markedNoShow: updatedReservations.length,
    createdEvents: eventsToInsert.length,
  })
}

export async function GET(request: Request) {
  return runNoShowDetection(request)
}

export async function POST(request: Request) {
  return runNoShowDetection(request)
}
