#!/usr/bin/env node
/**
 * Seed de usuarios fake + actividad de reservas.
 *
 * Requiere:
 *   NEXT_PUBLIC_SUPABASE_URL
 *   SUPABASE_SERVICE_ROLE_KEY
 *
 * Opcional:
 *   SEED_FAKE_USERS_COUNT (default 12)
 *   SEED_FAKE_RESERVATIONS_PER_USER (default 8)
 *   SEED_FAKE_FUTURE_PER_USER (default 2)
 *   SEED_DRY_RUN=1
 */

import { createClient } from "@supabase/supabase-js"

const NOTES = "Seed fake activity hu-rooms"
const USER_ID_PREFIX = "b2000000-0000-4000-8000-"
const RESERVATION_ID_PREFIX = "b3000000-0000-4000-8000-"
const NO_SHOW_EVENT_ID_PREFIX = "b4000000-0000-4000-8000-"
const ACTIVE_STATUSES = new Set(["confirmed", "checked_in"])

const FIRST_NAMES = [
  "Ana",
  "Bruno",
  "Camila",
  "Diego",
  "Elena",
  "Federico",
  "Gabriela",
  "Hugo",
  "Irene",
  "Javier",
  "Karina",
  "Lucas",
  "Micaela",
  "Nicolas",
  "Olivia",
  "Pablo",
  "Rocio",
  "Santiago",
  "Tamara",
  "Valentin",
]

const LAST_NAMES = [
  "Alvarez",
  "Benitez",
  "Castro",
  "Dominguez",
  "Estevez",
  "Fernandez",
  "Gonzalez",
  "Herrera",
  "Ibarra",
  "Juarez",
  "Lopez",
  "Martinez",
  "Navarro",
  "Ortega",
  "Pereyra",
  "Quiroga",
  "Ramirez",
  "Suarez",
  "Torres",
  "Vega",
]

const url = process.env.NEXT_PUBLIC_SUPABASE_URL?.trim()
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY?.trim()

const usersCount = Number.parseInt(process.env.SEED_FAKE_USERS_COUNT ?? "12", 10)
const reservationsPerUser = Number.parseInt(process.env.SEED_FAKE_RESERVATIONS_PER_USER ?? "8", 10)
const futureReservationsPerUser = Number.parseInt(process.env.SEED_FAKE_FUTURE_PER_USER ?? "2", 10)
const dryRun = process.env.SEED_DRY_RUN === "1" || process.env.SEED_DRY_RUN === "true"

function deterministicUuid(prefix, sequence) {
  const suffix = sequence.toString(16).padStart(12, "0")
  return `${prefix}${suffix}`
}

function dateAtHourUtc(baseDate, hour, dayDelta = 0) {
  const value = new Date(
    Date.UTC(
      baseDate.getUTCFullYear(),
      baseDate.getUTCMonth(),
      baseDate.getUTCDate(),
      hour,
      0,
      0,
      0,
    ),
  )
  value.setUTCDate(value.getUTCDate() + dayDelta)
  return value
}

function overlap(startA, endA, startB, endB) {
  return startA < endB && endA > startB
}

function buildFakeUsers(count) {
  const total = Number.isFinite(count) && count > 0 ? Math.trunc(count) : 12
  return Array.from({ length: total }).map((_, index) => {
    const id = deterministicUuid(USER_ID_PREFIX, index + 1)
    const email = `demo.user.${String(index + 1).padStart(2, "0")}@humand.co`
    const fullName = `${FIRST_NAMES[index % FIRST_NAMES.length]} ${LAST_NAMES[Math.floor(index / FIRST_NAMES.length) % LAST_NAMES.length]}`
    return {
      id,
      email,
      full_name: fullName,
      role: "user",
      is_active: true,
    }
  })
}

function buildActivityRows({ users, resources, perUser, futurePerUser }) {
  const now = new Date()
  const rows = []
  const statusPattern = [
    "confirmed",
    "checked_in",
    "completed",
    "cancelled",
    "no_show",
    "completed",
    "cancelled",
    "completed",
  ]

  let reservationSequence = 1

  for (let userIndex = 0; userIndex < users.length; userIndex += 1) {
    const user = users[userIndex]

    for (let slot = 0; slot < perUser; slot += 1) {
      const status = statusPattern[slot % statusPattern.length]
      const isFuture = slot < futurePerUser
      const dayDelta = isFuture ? slot + 1 + (userIndex % 3) : -(slot - futurePerUser + 1 + (userIndex % 5))
      const resource = resources[(userIndex * 3 + slot) % resources.length]
      const hour = 8 + ((userIndex * 2 + slot + (resource.seedIndex % 3)) % 10)
      const start = dateAtHourUtc(now, hour, dayDelta)
      const end = new Date(start.getTime() + 60 * 60 * 1000)

      const shouldCheckIn = status === "checked_in" || status === "completed"
      const checkedInAt = shouldCheckIn ? new Date(start.getTime() + 5 * 60 * 1000).toISOString() : null
      const reservationId = deterministicUuid(RESERVATION_ID_PREFIX, reservationSequence)
      const noShowEventId = deterministicUuid(NO_SHOW_EVENT_ID_PREFIX, reservationSequence)

      rows.push({
        sequence: reservationSequence,
        id: reservationId,
        resource_id: resource.id,
        resource_name: resource.name,
        user_id: user.id,
        user_email: user.email,
        start_at: start.toISOString(),
        end_at: end.toISOString(),
        status,
        checked_in_at: checkedInAt,
        notes: `${NOTES} | slot:${slot + 1}`,
        no_show_event_id: noShowEventId,
        no_show_detected_at: new Date(end.getTime() + 15 * 60 * 1000).toISOString(),
      })

      reservationSequence += 1
    }
  }

  return rows
}

async function ensureUsers(supabase, targetUsers) {
  const emails = targetUsers.map((user) => user.email)

  const { data: existingUsers, error: existingError } = await supabase
    .from("users")
    .select("id,email")
    .in("email", emails)

  if (existingError) {
    throw new Error(`No se pudieron consultar usuarios existentes: ${existingError.message}`)
  }

  const existingByEmail = new Map(
    (existingUsers ?? []).map((user) => [String(user.email).toLowerCase(), user.id]),
  )

  const missing = targetUsers.filter((user) => !existingByEmail.has(user.email.toLowerCase()))

  if (dryRun) {
    const resolved = targetUsers.map((target) => {
      const existingId = existingByEmail.get(target.email.toLowerCase())
      return {
        id: existingId ?? target.id,
        email: target.email,
      }
    })

    return {
      users: resolved,
      createdAuthCount: missing.length,
      insertedPublicCount: 0,
      existingPublicCount: (existingUsers ?? []).length,
    }
  }

  const createdAuthByEmail = new Map()
  let createdAuthCount = 0

  for (let index = 0; index < missing.length; index += 1) {
    const user = missing[index]
    const { data: authData, error: authError } = await supabase.auth.admin.createUser({
      email: user.email,
      password: `SeedFakeUsers!${String(index + 1).padStart(2, "0")}Aa1`,
      email_confirm: true,
      user_metadata: {
        full_name: user.full_name,
      },
    })

    if (authError) {
      const message = authError.message.toLowerCase()
      const alreadyExists =
        message.includes("already") || message.includes("registered") || message.includes("exists")
      if (!alreadyExists) {
        throw new Error(`No se pudo crear usuario auth (${user.email}): ${authError.message}`)
      }
    } else if (authData.user?.id) {
      createdAuthByEmail.set(user.email.toLowerCase(), authData.user.id)
      createdAuthCount += 1
    }
  }

  const { data: publicAfterAuth, error: publicAfterAuthError } = await supabase
    .from("users")
    .select("id,email")
    .in("email", emails)

  if (publicAfterAuthError) {
    throw new Error(`No se pudieron releer usuarios luego de auth seed: ${publicAfterAuthError.message}`)
  }

  const publicByEmail = new Map((publicAfterAuth ?? []).map((item) => [item.email.toLowerCase(), item.id]))
  const stillMissing = targetUsers.filter((user) => !publicByEmail.has(user.email.toLowerCase()))

  let insertedPublicCount = 0
  if (stillMissing.length > 0) {
    const { data: listData, error: listError } = await supabase.auth.admin.listUsers({
      page: 1,
      perPage: 1000,
    })

    if (listError) {
      throw new Error(`No se pudo listar auth.users para sincronizar perfiles: ${listError.message}`)
    }

    for (const authUser of listData.users ?? []) {
      const email = authUser.email?.toLowerCase()
      if (!email) continue
      if (!createdAuthByEmail.has(email)) {
        createdAuthByEmail.set(email, authUser.id)
      }
    }

    const unresolved = stillMissing.filter((user) => !createdAuthByEmail.has(user.email.toLowerCase()))
    if (unresolved.length > 0) {
      throw new Error(
        `No pude resolver ids de auth para: ${unresolved.map((user) => user.email).join(", ")}`,
      )
    }

    const payload = stillMissing.map((user) => ({
      id: createdAuthByEmail.get(user.email.toLowerCase()),
      email: user.email,
      full_name: user.full_name,
      role: "user",
      is_active: true,
    }))

    const { error: insertPublicError } = await supabase.from("users").insert(payload)
    if (insertPublicError) {
      throw new Error(`No se pudieron insertar perfiles públicos fake: ${insertPublicError.message}`)
    }
    insertedPublicCount = payload.length
  }

  const { data: resolvedUsers, error: resolvedError } = await supabase
    .from("users")
    .select("id,email")
    .in("email", emails)

  if (resolvedError) {
    throw new Error(`No se pudieron resolver usuarios fake: ${resolvedError.message}`)
  }

  return {
    users: (resolvedUsers ?? []).map((user) => ({ id: user.id, email: user.email })),
    createdAuthCount,
    insertedPublicCount,
    existingPublicCount: (existingUsers ?? []).length,
  }
}

async function fetchResources(supabase) {
  const { data, error } = await supabase
    .from("resources")
    .select("id,name")
    .or("is_active.is.null,is_active.eq.true")
    .order("name")
    .limit(250)

  if (error) {
    throw new Error(`No se pudieron consultar recursos: ${error.message}`)
  }

  if (!data || data.length === 0) {
    throw new Error("No hay recursos activos en la base para generar actividad fake.")
  }

  return data.map((resource, index) => ({
    id: resource.id,
    name: resource.name,
    seedIndex: index,
  }))
}

async function insertActivity(supabase, activityRows) {
  const ids = activityRows.map((row) => row.id)
  const { data: existingRows, error: existingError } = await supabase
    .from("reservations")
    .select("id,status")
    .in("id", ids)

  if (existingError) {
    throw new Error(`No se pudieron consultar reservas existentes: ${existingError.message}`)
  }

  const existingById = new Map((existingRows ?? []).map((row) => [row.id, row.status]))
  const activeByResource = new Map()

  let inserted = 0
  let skippedExisting = 0
  let skippedOverlapLocal = 0
  let skippedOverlapRemote = 0
  const noShowReservations = []

  for (const row of activityRows) {
    if (existingById.has(row.id)) {
      skippedExisting += 1
      if (existingById.get(row.id) === "no_show") {
        noShowReservations.push({
          reservationId: row.id,
          userId: row.user_id,
          eventId: row.no_show_event_id,
          detectedAt: row.no_show_detected_at,
        })
      }
      continue
    }

    if (ACTIVE_STATUSES.has(row.status)) {
      const localWindows = activeByResource.get(row.resource_id) ?? []
      const hasLocalOverlap = localWindows.some((window) =>
        overlap(
          new Date(row.start_at).getTime(),
          new Date(row.end_at).getTime(),
          window.startAtMs,
          window.endAtMs,
        ),
      )

      if (hasLocalOverlap) {
        skippedOverlapLocal += 1
        continue
      }

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

      if (conflictError) {
        throw new Error(`No se pudo verificar solape de reservas: ${conflictError.message}`)
      }

      if ((conflicts ?? []).length > 0) {
        skippedOverlapRemote += 1
        continue
      }
    }

    if (!dryRun) {
      const { error: insertError } = await supabase.from("reservations").insert({
        id: row.id,
        resource_id: row.resource_id,
        user_id: row.user_id,
        start_at: row.start_at,
        end_at: row.end_at,
        status: row.status,
        checked_in_at: row.checked_in_at,
        notes: row.notes,
      })

      if (insertError) {
        throw new Error(`No se pudo insertar reserva fake (${row.id}): ${insertError.message}`)
      }
    }

    inserted += 1

    if (ACTIVE_STATUSES.has(row.status)) {
      const localWindows = activeByResource.get(row.resource_id) ?? []
      localWindows.push({
        startAtMs: new Date(row.start_at).getTime(),
        endAtMs: new Date(row.end_at).getTime(),
      })
      activeByResource.set(row.resource_id, localWindows)
    }

    if (row.status === "no_show") {
      noShowReservations.push({
        reservationId: row.id,
        userId: row.user_id,
        eventId: row.no_show_event_id,
        detectedAt: row.no_show_detected_at,
      })
    }
  }

  return {
    inserted,
    skippedExisting,
    skippedOverlapLocal,
    skippedOverlapRemote,
    noShowReservations,
  }
}

async function insertNoShowEvents(supabase, noShowReservations) {
  if (noShowReservations.length === 0) {
    return { inserted: 0, skippedExisting: 0 }
  }

  const ids = noShowReservations.map((item) => item.eventId)
  const { data: existing, error: existingError } = await supabase
    .from("no_show_events")
    .select("id")
    .in("id", ids)

  if (existingError) {
    throw new Error(`No se pudieron consultar no_show_events existentes: ${existingError.message}`)
  }

  const existingIds = new Set((existing ?? []).map((item) => item.id))
  const pending = noShowReservations.filter((item) => !existingIds.has(item.eventId))

  if (!dryRun && pending.length > 0) {
    const payload = pending.map((item) => ({
      id: item.eventId,
      reservation_id: item.reservationId,
      user_id: item.userId,
      detected_at: item.detectedAt,
      notified_at: null,
    }))

    const { error: insertError } = await supabase.from("no_show_events").insert(payload)
    if (insertError) {
      throw new Error(`No se pudieron insertar no_show_events fake: ${insertError.message}`)
    }
  }

  return {
    inserted: pending.length,
    skippedExisting: existingIds.size,
  }
}

async function main() {
  if (!url || !serviceKey) {
    throw new Error("Faltan NEXT_PUBLIC_SUPABASE_URL o SUPABASE_SERVICE_ROLE_KEY en el entorno.")
  }

  if (futureReservationsPerUser >= reservationsPerUser) {
    throw new Error("SEED_FAKE_FUTURE_PER_USER debe ser menor que SEED_FAKE_RESERVATIONS_PER_USER.")
  }

  const supabase = createClient(url, serviceKey, {
    auth: { persistSession: false, autoRefreshToken: false },
  })

  const fakeUsers = buildFakeUsers(usersCount)
  const userResult = await ensureUsers(supabase, fakeUsers)
  const resources = await fetchResources(supabase)

  const resolvedUsers = fakeUsers.map((target) => {
    const resolved = userResult.users.find((item) => item.email.toLowerCase() === target.email.toLowerCase())
    if (!resolved) {
      throw new Error(`No se pudo resolver el usuario fake ${target.email} luego del seed.`)
    }
    return resolved
  })

  const activityRows = buildActivityRows({
    users: resolvedUsers,
    resources,
    perUser: reservationsPerUser,
    futurePerUser: futureReservationsPerUser,
  })

  const activityResult = await insertActivity(supabase, activityRows)
  const noShowResult = await insertNoShowEvents(supabase, activityResult.noShowReservations)

  console.log(
    JSON.stringify(
      {
        mode: dryRun ? "dry-run" : "write",
        users: {
          target: fakeUsers.length,
          created_auth: userResult.createdAuthCount,
          inserted_public_profiles: userResult.insertedPublicCount,
          already_present_public_profiles: userResult.existingPublicCount,
        },
        reservations: {
          target: activityRows.length,
          inserted: activityResult.inserted,
          skipped_existing: activityResult.skippedExisting,
          skipped_overlap_local: activityResult.skippedOverlapLocal,
          skipped_overlap_remote: activityResult.skippedOverlapRemote,
        },
        no_show_events: {
          target_from_no_show_reservations: activityResult.noShowReservations.length,
          inserted: noShowResult.inserted,
          skipped_existing: noShowResult.skippedExisting,
        },
        notes_tag: NOTES,
      },
      null,
      2,
    ),
  )
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : error)
  process.exit(1)
})
