"use server"

import { revalidatePath } from "next/cache"
import { and, eq } from "drizzle-orm"
import { db } from "@/lib/db"
import { webhook_endpoints } from "@/lib/db/schema"
import { getSessionUser } from "@/lib/session"
import { getAccountContext } from "@/lib/account"
import { webhookEndpointSchema } from "@/lib/validations"
import { isWebhookEvent, type WebhookEvent } from "@/lib/webhooks/events"
import { assertSafeWebhookUrl, WebhookUrlError } from "@/lib/webhooks/ssrf"
import { generateWebhookSecret } from "@/lib/webhooks/deliver"
import { deliverTestPing } from "@/lib/webhooks/emit"

// ============================================================================
// Webhook endpoint management (dashboard, session-authed).
//
// Endpoints belong to the ACCOUNT OWNER (team-aware) so every event across the
// portfolio is delivered. Writes require canWrite (viewers are read-only). The
// signing secret is stored so it can be shown in the dashboard and used to sign
// deliveries; it is not a bearer credential.
// ============================================================================

const SETTINGS_PATH = "/settings/webhooks"

export type WebhookEndpointDTO = {
  id: string
  url: string
  description: string | null
  events: string[]
  secret: string
  status: "active" | "disabled"
  source: "dashboard" | "api" | "zapier"
  last_success_at: string | null
  last_error_at: string | null
  last_error: string | null
  failure_count: number
  created_at: string
}

function toDTO(row: typeof webhook_endpoints.$inferSelect): WebhookEndpointDTO {
  return {
    id: row.id,
    url: row.url,
    description: row.description,
    events: row.events,
    secret: row.secret,
    status: row.status,
    source: row.source,
    last_success_at: row.last_success_at,
    last_error_at: row.last_error_at,
    last_error: row.last_error,
    failure_count: row.failure_count,
    created_at: row.created_at,
  }
}

/** Resolve the writing account owner or throw a user-facing error. */
async function requireWriter(): Promise<string> {
  const user = await getSessionUser()
  if (!user) throw new Error("Unauthorized")
  const ctx = await getAccountContext(user.id)
  if (!ctx.canWrite) throw new Error("You do not have permission to manage webhooks.")
  return ctx.ownerId
}

function sanitizeEvents(events: unknown): WebhookEvent[] {
  if (!Array.isArray(events)) return []
  return Array.from(new Set(events.filter(isWebhookEvent)))
}

export async function createWebhookEndpoint(input: {
  url: string
  events: string[]
  description?: string
}): Promise<WebhookEndpointDTO> {
  const ownerId = await requireWriter()

  const parsed = webhookEndpointSchema.safeParse(input)
  if (!parsed.success) {
    throw new Error(parsed.error.issues[0]?.message ?? "Invalid webhook configuration")
  }

  try {
    await assertSafeWebhookUrl(parsed.data.url)
  } catch (e) {
    throw new Error(e instanceof WebhookUrlError ? e.message : "Invalid webhook URL")
  }

  const [row] = await db
    .insert(webhook_endpoints)
    .values({
      user_id: ownerId,
      url: parsed.data.url,
      description: parsed.data.description || null,
      events: sanitizeEvents(parsed.data.events),
      secret: generateWebhookSecret(),
      source: "dashboard",
    })
    .returning()

  revalidatePath(SETTINGS_PATH)
  return toDTO(row)
}

export async function updateWebhookEndpoint(
  id: string,
  input: { url?: string; events?: string[]; description?: string; status?: "active" | "disabled" }
): Promise<WebhookEndpointDTO> {
  const ownerId = await requireWriter()
  if (typeof id !== "string" || !id) throw new Error("Invalid endpoint id")

  const existing = await db.query.webhook_endpoints.findFirst({
    where: and(eq(webhook_endpoints.id, id), eq(webhook_endpoints.user_id, ownerId)),
  })
  if (!existing) throw new Error("Webhook not found")

  const patch: Partial<typeof webhook_endpoints.$inferInsert> = {}

  if (input.url !== undefined) {
    const parsed = webhookEndpointSchema.shape.url.safeParse(input.url)
    if (!parsed.success) throw new Error(parsed.error.issues[0]?.message ?? "Invalid URL")
    try {
      await assertSafeWebhookUrl(parsed.data)
    } catch (e) {
      throw new Error(e instanceof WebhookUrlError ? e.message : "Invalid webhook URL")
    }
    patch.url = parsed.data
  }
  if (input.events !== undefined) patch.events = sanitizeEvents(input.events)
  if (input.description !== undefined) patch.description = input.description.slice(0, 200) || null
  if (input.status !== undefined) {
    if (input.status !== "active" && input.status !== "disabled") throw new Error("Invalid status")
    patch.status = input.status
  }

  const [row] = await db
    .update(webhook_endpoints)
    .set(patch)
    .where(and(eq(webhook_endpoints.id, id), eq(webhook_endpoints.user_id, ownerId)))
    .returning()

  revalidatePath(SETTINGS_PATH)
  return toDTO(row)
}

export async function deleteWebhookEndpoint(id: string): Promise<void> {
  const ownerId = await requireWriter()
  if (typeof id !== "string" || !id) throw new Error("Invalid endpoint id")

  await db
    .delete(webhook_endpoints)
    .where(and(eq(webhook_endpoints.id, id), eq(webhook_endpoints.user_id, ownerId)))

  revalidatePath(SETTINGS_PATH)
}

export async function rotateWebhookSecret(id: string): Promise<{ secret: string }> {
  const ownerId = await requireWriter()
  if (typeof id !== "string" || !id) throw new Error("Invalid endpoint id")

  const secret = generateWebhookSecret()
  const [row] = await db
    .update(webhook_endpoints)
    .set({ secret })
    .where(and(eq(webhook_endpoints.id, id), eq(webhook_endpoints.user_id, ownerId)))
    .returning({ id: webhook_endpoints.id })
  if (!row) throw new Error("Webhook not found")

  revalidatePath(SETTINGS_PATH)
  return { secret }
}

export async function sendTestWebhook(
  id: string
): Promise<{ ok: boolean; responseStatus: number | null; error: string | null }> {
  const ownerId = await requireWriter()
  if (typeof id !== "string" || !id) throw new Error("Invalid endpoint id")

  const endpoint = await db.query.webhook_endpoints.findFirst({
    where: and(eq(webhook_endpoints.id, id), eq(webhook_endpoints.user_id, ownerId)),
  })
  if (!endpoint) throw new Error("Webhook not found")

  const result = await deliverTestPing(endpoint)
  revalidatePath(SETTINGS_PATH)
  return result
}
