/**
 * /v1/leads routes — zapier-make-integration (wave-13).
 * GET /v1/leads        — list leads (cursor-paginated, optional ?email= filter)
 * POST /v1/leads       — create lead
 * PATCH /v1/leads/:id  — update lead stage
 *
 * Auth: scope `leads:read` (GET) / `leads:write` (POST, PATCH).
 * Tier: Business+ required; 403 tier_required for Freelancer.
 */
import { z } from 'zod'
import type { NewLead } from '@zync/db'
import { leads, leadActivities } from '@zync/db'
import { eq, and, isNull, desc, lt, sql } from 'drizzle-orm'
import {
  serializeLead,
  buildPaginatedResult,
  paginationSchema,
  decodeCursor,
  hasScope,
  errForbiddenScope,
} from '../index'
import type { PublicApiContext } from '../app'

const createLeadSchema = z.object({
  name: z.string().min(1),
  email: z.string().email().optional(),
  phone: z.string().optional(),
  source: z.enum([
    'manual', 'form', 'webhook', 'facebook', 'google', 'linkedin',
    'instagram', 'zapier', 'make', 'referral', 'cold_outreach',
  ]).optional(),
  company: z.string().optional(),
  estimated_value: z.number().positive().optional(),
})

const updateLeadSchema = z.object({
  stage: z.enum(['NEW', 'CONTACTED', 'QUALIFIED', 'PROPOSAL', 'WON', 'LOST']),
})

type CreateLeadInput = z.infer<typeof createLeadSchema>
type UpdateLeadInput = z.infer<typeof updateLeadSchema>

/** GET /v1/leads */
export async function listLeads(ctx: PublicApiContext): Promise<Response> {
  if (!hasScope(ctx.scopes, 'leads:read')) return errForbiddenScope('leads:read')
  const { db, tenantId } = ctx
  const url = new URL(ctx.request.url)

  const pageResult = paginationSchema.safeParse({
    limit: url.searchParams.get('limit') ?? '20',
    cursor: url.searchParams.get('cursor') ?? undefined,
  })
  if (!pageResult.success) {
    return new Response(JSON.stringify({ error: 'validation_error', message: 'Invalid pagination params' }), {
      status: 422, headers: { 'Content-Type': 'application/json' },
    })
  }
  const { limit, cursor } = pageResult.data
  const email = url.searchParams.get('email')

  const cursorPayload = cursor ? decodeCursor(cursor) : null

  const conditions = [
    eq(leads.tenantId, tenantId),
    isNull(leads.archivedAt),
  ] as ReturnType<typeof eq>[]

  if (email) {
    conditions.push(eq(leads.email, email))
  }
  if (cursorPayload) {
    conditions.push(
      lt(leads.createdAt, new Date(cursorPayload.created_at)),
    )
  }

  const rows = await db
    .select()
    .from(leads)
    .where(and(...conditions))
    .orderBy(desc(leads.createdAt))
    .limit(limit + 1)

  const countConditions = [
    eq(leads.tenantId, tenantId),
    isNull(leads.archivedAt),
  ] as ReturnType<typeof eq>[]
  if (email) countConditions.push(eq(leads.email, email))

  const countResult = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(leads)
    .where(and(...countConditions))
  const total = countResult[0]?.count ?? 0

  const sliced = rows.slice(0, limit)
  const serialized = sliced.map((r) => ({
    ...serializeLead({
      id: r.id,
      name: r.name,
      email: r.email,
      phone: r.phone,
      company: r.company,
      stage: r.stage,
      source: r.source,
      estimatedValue: r.estimatedValue,
      customerId: r.customerId,
      assignedTo: r.assignedTo,
      createdAt: r.createdAt,
    }),
    created_at: r.createdAt.toISOString(),
  }))

  const result = buildPaginatedResult(
    serialized.map((s) => ({ ...s, id: s.id, created_at: s.created_at })),
    limit,
    total,
  )

  return new Response(JSON.stringify(result), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  })
}

/** POST /v1/leads */
export async function createLead(ctx: PublicApiContext): Promise<Response> {
  if (!hasScope(ctx.scopes, 'leads:write')) return errForbiddenScope('leads:write')
  const { db, tenantId, oauthClientId } = ctx

  let body: unknown
  try {
    body = await ctx.request.json()
  } catch {
    return new Response(JSON.stringify({ error: 'validation_error', message: 'Invalid JSON body' }), {
      status: 422, headers: { 'Content-Type': 'application/json' },
    })
  }

  const parsed = createLeadSchema.safeParse(body)
  if (!parsed.success) {
    return new Response(
      JSON.stringify({ error: 'validation_error', message: parsed.error.issues[0]?.message ?? 'Invalid input', field: parsed.error.issues[0]?.path.join('.') }),
      { status: 422, headers: { 'Content-Type': 'application/json' } },
    )
  }

  const data: CreateLeadInput = parsed.data

  // Derive source from OAuth client id if not explicitly set
  let source = data.source ?? 'manual'
  if (!data.source) {
    if (oauthClientId === 'zapier_zync') source = 'zapier'
    else if (oauthClientId === 'make_zync') source = 'make'
  }

  // Compute stage_position as max + 1 for the NEW stage
  const maxResult = await db
    .select({ maxPos: sql<string | null>`max(stage_position)` })
    .from(leads)
    .where(and(eq(leads.tenantId, tenantId), eq(leads.stage, 'NEW'), isNull(leads.archivedAt)))

  const maxPos = maxResult[0]?.maxPos ? parseFloat(maxResult[0].maxPos) : 0
  const stagePosition = String(maxPos + 1)

  const newLead: NewLead = {
    tenantId,
    name: data.name,
    email: data.email ?? null,
    phone: data.phone ?? null,
    company: data.company ?? null,
    stage: 'NEW',
    stagePosition,
    source,
    estimatedValue: data.estimated_value != null ? String(data.estimated_value) : null,
  }

  const [inserted] = await db.insert(leads).values(newLead).returning()
  if (!inserted) {
    return new Response(JSON.stringify({ error: 'internal_error', message: 'Failed to create lead' }), {
      status: 500, headers: { 'Content-Type': 'application/json' },
    })
  }

  return new Response(
    JSON.stringify(serializeLead({
      id: inserted.id,
      name: inserted.name,
      email: inserted.email,
      phone: inserted.phone,
      company: inserted.company,
      stage: inserted.stage,
      source: inserted.source,
      estimatedValue: inserted.estimatedValue,
      customerId: inserted.customerId,
      assignedTo: inserted.assignedTo,
      createdAt: inserted.createdAt,
    })),
    { status: 201, headers: { 'Content-Type': 'application/json' } },
  )
}

/** PATCH /v1/leads/:id */
export async function updateLeadStage(ctx: PublicApiContext, id: string): Promise<Response> {
  if (!hasScope(ctx.scopes, 'leads:write')) return errForbiddenScope('leads:write')
  const { db, tenantId } = ctx

  let body: unknown
  try {
    body = await ctx.request.json()
  } catch {
    return new Response(JSON.stringify({ error: 'validation_error', message: 'Invalid JSON body' }), {
      status: 422, headers: { 'Content-Type': 'application/json' },
    })
  }

  const parsed = updateLeadSchema.safeParse(body)
  if (!parsed.success) {
    return new Response(
      JSON.stringify({ error: 'validation_error', message: parsed.error.issues[0]?.message ?? 'Invalid input' }),
      { status: 422, headers: { 'Content-Type': 'application/json' } },
    )
  }

  const { stage } = parsed.data as UpdateLeadInput

  // Fetch existing lead
  const [existing] = await db
    .select()
    .from(leads)
    .where(and(eq(leads.id, id), eq(leads.tenantId, tenantId), isNull(leads.archivedAt)))
    .limit(1)

  if (!existing) {
    return new Response(JSON.stringify({ error: 'not_found', message: 'Lead not found' }), {
      status: 404, headers: { 'Content-Type': 'application/json' },
    })
  }

  // Compute new stage_position as max + 1 for target stage
  const maxResult = await db
    .select({ maxPos: sql<string | null>`max(stage_position)` })
    .from(leads)
    .where(and(eq(leads.tenantId, tenantId), eq(leads.stage, stage), isNull(leads.archivedAt)))

  const maxPos = maxResult[0]?.maxPos ? parseFloat(maxResult[0].maxPos) : 0
  const stagePosition = String(maxPos + 1)

  const [updated] = await db
    .update(leads)
    .set({ stage, stagePosition, updatedAt: new Date() })
    .where(and(eq(leads.id, id), eq(leads.tenantId, tenantId)))
    .returning()

  if (!updated) {
    return new Response(JSON.stringify({ error: 'internal_error', message: 'Failed to update lead' }), {
      status: 500, headers: { 'Content-Type': 'application/json' },
    })
  }

  // Log lead_activities row for stage change
  if (existing.stage !== stage) {
    await db.insert(leadActivities).values({
      tenantId,
      leadId: id,
      type: 'stage_changed',
      metadata: { from: existing.stage, to: stage },
    })
  }

  return new Response(
    JSON.stringify(serializeLead({
      id: updated.id,
      name: updated.name,
      email: updated.email,
      phone: updated.phone,
      company: updated.company,
      stage: updated.stage,
      source: updated.source,
      estimatedValue: updated.estimatedValue,
      customerId: updated.customerId,
      assignedTo: updated.assignedTo,
      createdAt: updated.createdAt,
    })),
    { status: 200, headers: { 'Content-Type': 'application/json' } },
  )
}
