/**
 * Campaigns queries — marketing-catalogs-campaigns (wave-9 leaf 3).
 * Lightweight marketing campaign CRUD + activate.
 *
 * All functions take (db, tenantId, ...) — tenant isolation enforced here.
 * No raw Drizzle from routes (no-raw-drizzle-from-routes ESLint rule).
 */
import { eq, and, desc } from 'drizzle-orm'
import { z } from 'zod'
import type { Db, DbTx } from '../client'
import { campaigns } from '../schema/proposals'

// ── Zod schemas ───────────────────────────────────────────────────────────────

export const createCampaignSchema = z.object({
  name: z.string().min(1).max(255),
  type: z.enum(['email', 'sms', 'push']).optional(),
  targetSegment: z.record(z.unknown()).optional(),
  scheduledAt: z.string().datetime().optional(),
})

export const updateCampaignSchema = z.object({
  name: z.string().min(1).max(255).optional(),
  type: z.enum(['email', 'sms', 'push']).optional(),
  status: z.enum(['draft', 'active', 'completed']).optional(),
  targetSegment: z.record(z.unknown()).optional(),
  scheduledAt: z.string().datetime().optional(),
  isActive: z.boolean().optional(),
})

export type CreateCampaignInput = z.infer<typeof createCampaignSchema>
export type UpdateCampaignInput = z.infer<typeof updateCampaignSchema>

// ── Serialization ─────────────────────────────────────────────────────────────

export interface CampaignObject {
  id: string
  tenantId: string
  name: string
  type: string
  status: string
  targetSegment: Record<string, unknown> | null
  scheduledAt: string | null
  sentCount: number
  isActive: boolean
  createdAt: string
  updatedAt: string
}

function toDateOrNull(value: Date | string | null | undefined): Date | null {
  if (value == null) return null
  return value instanceof Date ? value : new Date(value)
}

export type CampaignRowRecord = typeof campaigns.$inferSelect

export interface UpsertCampaignRowInput {
  id: string
  tenantId: string
  name: string
  type?: string
  status?: string
  targetSegment?: Record<string, unknown> | null
  scheduledAt?: Date | string | null
  sentCount?: number
  isActive?: boolean
  createdAt?: Date | string
  updatedAt?: Date | string
}

function serializeCampaign(row: typeof campaigns.$inferSelect): CampaignObject {
  return {
    id: row.id,
    tenantId: row.tenantId,
    name: row.name,
    type: row.type,
    status: row.status,
    targetSegment: row.targetSegment as Record<string, unknown> | null,
    scheduledAt: row.scheduledAt ? row.scheduledAt.toISOString() : null,
    sentCount: row.sentCount,
    isActive: row.isActive,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
  }
}

export async function listCampaignRowsForTenant(
  db: Db | DbTx,
  tenantId: string,
): Promise<CampaignRowRecord[]> {
  return db.select().from(campaigns).where(eq(campaigns.tenantId, tenantId))
}

export async function upsertCampaignRow(
  db: Db | DbTx,
  row: UpsertCampaignRowInput,
): Promise<CampaignRowRecord> {
  const [existing] = await db
    .select({ id: campaigns.id })
    .from(campaigns)
    .where(and(eq(campaigns.tenantId, row.tenantId), eq(campaigns.id, row.id)))
    .limit(1)

  const values = {
    tenantId: row.tenantId,
    name: row.name,
    type: row.type ?? 'email',
    status: row.status ?? 'draft',
    targetSegment: row.targetSegment ?? null,
    scheduledAt: toDateOrNull(row.scheduledAt),
    sentCount: row.sentCount ?? 0,
    isActive: row.isActive ?? true,
    createdAt: row.createdAt ? new Date(row.createdAt) : new Date(),
    updatedAt: row.updatedAt ? new Date(row.updatedAt) : new Date(),
  }

  if (existing) {
    const [updated] = await db
      .update(campaigns)
      .set(values)
      .where(and(eq(campaigns.tenantId, row.tenantId), eq(campaigns.id, row.id)))
      .returning()
    if (!updated) throw new Error('Campaign not found after update')
    return updated
  }

  const [inserted] = await db
    .insert(campaigns)
    .values({
      id: row.id,
      ...values,
    })
    .returning()
  if (!inserted) throw new Error('Campaign not found after insert')
  return inserted
}

// ── Query functions ───────────────────────────────────────────────────────────

export async function listCampaigns(
  db: Db,
  tenantId: string,
  opts: { status?: string } = {},
): Promise<CampaignObject[]> {
  const conditions = [eq(campaigns.tenantId, tenantId)]
  if (opts.status) conditions.push(eq(campaigns.status, opts.status))

  const rows = await db
    .select()
    .from(campaigns)
    .where(and(...conditions))
    .orderBy(desc(campaigns.createdAt))

  return rows.map(serializeCampaign)
}

export async function getCampaign(
  db: Db,
  tenantId: string,
  id: string,
): Promise<CampaignObject | null> {
  const [row] = await db
    .select()
    .from(campaigns)
    .where(and(eq(campaigns.tenantId, tenantId), eq(campaigns.id, id)))
  return row ? serializeCampaign(row) : null
}

export async function createCampaign(
  db: Db,
  tenantId: string,
  input: CreateCampaignInput,
): Promise<CampaignObject> {
  const [row] = await db
    .insert(campaigns)
    .values({
      tenantId,
      name: input.name,
      type: input.type ?? 'email',
      status: 'draft',
      targetSegment: input.targetSegment ?? null,
      scheduledAt: input.scheduledAt ? new Date(input.scheduledAt) : null,
      sentCount: 0,
      isActive: true,
    })
    .returning()
  if (!row) throw new Error('Campaign not found after insert')
  return serializeCampaign(row)
}

export async function updateCampaign(
  db: Db,
  tenantId: string,
  id: string,
  input: UpdateCampaignInput,
): Promise<CampaignObject> {
  const updates: Partial<typeof campaigns.$inferInsert> = { updatedAt: new Date() }
  if (input.name !== undefined) updates.name = input.name
  if (input.type !== undefined) updates.type = input.type
  if (input.status !== undefined) updates.status = input.status
  if (input.targetSegment !== undefined) updates.targetSegment = input.targetSegment ?? null
  if (input.scheduledAt !== undefined) updates.scheduledAt = input.scheduledAt ? new Date(input.scheduledAt) : null
  if (input.isActive !== undefined) updates.isActive = input.isActive

  const [row] = await db
    .update(campaigns)
    .set(updates)
    .where(and(eq(campaigns.tenantId, tenantId), eq(campaigns.id, id)))
    .returning()
  if (!row) throw new Error('Campaign not found')
  return serializeCampaign(row)
}

export async function activateCampaign(
  db: Db,
  tenantId: string,
  id: string,
): Promise<CampaignObject> {
  const [row] = await db
    .update(campaigns)
    .set({ status: 'active', isActive: true, updatedAt: new Date() })
    .where(and(eq(campaigns.tenantId, tenantId), eq(campaigns.id, id)))
    .returning()
  if (!row) throw new Error('Campaign not found')
  return serializeCampaign(row)
}
