import { and, asc, desc, eq, gte, ilike, inArray, or, sql } from "drizzle-orm"
import type { PgTable } from "drizzle-orm/pg-core"
import { db } from "@/lib/db"
import {
  profiles,
  user,
  properties,
  units,
  tenants,
  rent_payments,
  maintenance_requests,
  leases,
  expenses,
  usage_events,
  activity_log,
  admin_audit_log,
  follow_up_rules,
} from "@/lib/db/schema"
import { PLAN_PRICES } from "@/lib/stripe/plans"
import type { Plan } from "@/types"

// ── helpers ───────────────────────────────────────────────────────────────────
const PLANS: Plan[] = ["starter", "pro", "landlord", "lifetime"]

function utcMonthStartISO(d = new Date()) {
  return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)).toISOString()
}

/** count(*) over a whole table. */
async function countRows(tbl: PgTable): Promise<number> {
  const r = await db.select({ c: sql<number>`count(*)::int` }).from(tbl)
  return r[0].c
}

/** count(*) over a table filtered to one owner (every app table has user_id). */
async function countRowsForUser(tbl: PgTable, userId: string): Promise<number> {
  const r = await db
    .select({ c: sql<number>`count(*)::int` })
    .from(tbl)
    .where(sql`user_id = ${userId}`)
  return r[0].c
}

// ── plan distribution + MRR ────────────────────────────────────────────────────
export async function getPlanDistribution(): Promise<Record<Plan, number>> {
  const rows = await db
    .select({ plan: profiles.plan, count: sql<number>`count(*)::int` })
    .from(profiles)
    .groupBy(profiles.plan)
  const dist: Record<Plan, number> = { starter: 0, pro: 0, landlord: 0, lifetime: 0 }
  for (const r of rows) if (r.plan in dist) dist[r.plan as Plan] = r.count
  return dist
}

export function computeMrr(dist: Record<Plan, number>) {
  const proAmt = PLAN_PRICES.pro?.amount ?? 29
  const landlordAmt = PLAN_PRICES.landlord?.amount ?? 59
  const lifetimeAmt = PLAN_PRICES.lifetime?.amount ?? 199
  const mrr = dist.pro * proAmt + dist.landlord * landlordAmt
  return { mrr, arr: mrr * 12, lifetimeRevenue: dist.lifetime * lifetimeAmt }
}

// ── overview KPIs ───────────────────────────────────────────────────────────────
export async function getAdminOverviewStats() {
  const monthStart = utcMonthStartISO()
  const thirtyDaysAgo = new Date(Date.now() - 30 * 86_400_000).toISOString()

  const [
    totalUsersR,
    newSignupsR,
    totalPropertiesR,
    totalUnitsR,
    totalTenantsR,
    aiR,
    rentR,
    activeR,
    dist,
  ] = await Promise.all([
    db.select({ c: sql<number>`count(*)::int` }).from(profiles),
    db.select({ c: sql<number>`count(*)::int` }).from(profiles).where(gte(profiles.created_at, monthStart)),
    db.select({ c: sql<number>`count(*)::int` }).from(properties),
    db.select({ c: sql<number>`count(*)::int` }).from(units),
    db.select({ c: sql<number>`count(*)::int` }).from(tenants),
    db.select({ c: sql<number>`count(*)::int` }).from(usage_events).where(gte(usage_events.created_at, monthStart)),
    db
      .select({ s: sql<number>`coalesce(sum(${rent_payments.amount}), 0)::float` })
      .from(rent_payments)
      .where(and(eq(rent_payments.status, "paid"), gte(rent_payments.due_date, monthStart.slice(0, 10)))),
    db
      .select({ c: sql<number>`count(distinct ${activity_log.user_id})::int` })
      .from(activity_log)
      .where(gte(activity_log.created_at, thirtyDaysAgo)),
    getPlanDistribution(),
  ])

  const totalUsers = totalUsersR[0].c
  const paidUsers = dist.pro + dist.landlord + dist.lifetime
  const { mrr, arr, lifetimeRevenue } = computeMrr(dist)

  return {
    totalUsers,
    newSignupsThisMonth: newSignupsR[0].c,
    totalProperties: totalPropertiesR[0].c,
    totalUnits: totalUnitsR[0].c,
    totalTenants: totalTenantsR[0].c,
    aiCallsThisMonth: aiR[0].c,
    rentCollectedThisMonth: rentR[0].s ?? 0,
    activeUsers30d: activeR[0].c,
    paidUsers,
    freeUsers: totalUsers - paidUsers,
    planDistribution: dist,
    mrr,
    arr,
    lifetimeRevenue,
  }
}

// ── signups trend (last N months) ──────────────────────────────────────────────
export async function getSignupsTrend(months = 6) {
  const start = new Date()
  start.setMonth(start.getMonth() - (months - 1))
  const startISO = utcMonthStartISO(start)

  const rows = await db
    .select({ created_at: profiles.created_at })
    .from(profiles)
    .where(gte(profiles.created_at, startISO))

  const buckets: Record<string, number> = {}
  for (let i = months - 1; i >= 0; i--) {
    const d = new Date()
    d.setMonth(d.getMonth() - i)
    buckets[`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`] = 0
  }
  for (const r of rows) {
    const key = String(r.created_at).slice(0, 7)
    if (key in buckets) buckets[key]++
  }
  return Object.entries(buckets).map(([month, count]) => ({
    month,
    label: new Date(month + "-01").toLocaleDateString("en-US", { month: "short" }),
    count,
  }))
}

// ── at-risk subscriptions ───────────────────────────────────────────────────────
export async function getAtRiskSubscriptions() {
  return db
    .select({
      id: profiles.id,
      email: profiles.email,
      full_name: profiles.full_name,
      plan: profiles.plan,
      subscription_status: profiles.subscription_status,
      plan_expires_at: profiles.plan_expires_at,
    })
    .from(profiles)
    .where(inArray(profiles.subscription_status, ["past_due", "unpaid", "incomplete"]))
    .limit(100)
}

// ── AI usage aggregates ─────────────────────────────────────────────────────────
export async function getAiUsageAggregates() {
  const monthStart = utcMonthStartISO()
  const [byType, totalMonth, topRaw] = await Promise.all([
    db
      .select({ event_type: usage_events.event_type, count: sql<number>`count(*)::int` })
      .from(usage_events)
      .groupBy(usage_events.event_type)
      .orderBy(desc(sql`count(*)`)),
    db.select({ c: sql<number>`count(*)::int` }).from(usage_events).where(gte(usage_events.created_at, monthStart)),
    db
      .select({ user_id: usage_events.user_id, count: sql<number>`count(*)::int` })
      .from(usage_events)
      .where(gte(usage_events.created_at, monthStart))
      .groupBy(usage_events.user_id)
      .orderBy(desc(sql`count(*)`))
      .limit(10),
  ])

  const ids = topRaw.map((u) => u.user_id)
  const emails = ids.length
    ? await db.select({ id: profiles.id, email: profiles.email }).from(profiles).where(inArray(profiles.id, ids))
    : []
  const emailMap = Object.fromEntries(emails.map((e) => [e.id, e.email]))
  const topUsers = topRaw.map((u) => ({ ...u, email: emailMap[u.user_id] ?? u.user_id }))

  return { byType, totalThisMonth: totalMonth[0].c, topUsers }
}

// ── platform-wide activity feed + admin audit log ───────────────────────────────
export async function getPlatformActivity({ limit = 50, offset = 0 } = {}) {
  return db
    .select({
      id: activity_log.id,
      type: activity_log.type,
      title: activity_log.title,
      description: activity_log.description,
      created_at: activity_log.created_at,
      user_email: profiles.email,
    })
    .from(activity_log)
    .leftJoin(profiles, eq(activity_log.user_id, profiles.id))
    .orderBy(desc(activity_log.created_at))
    .limit(limit)
    .offset(offset)
}

export async function getAdminAuditLog({ limit = 50, offset = 0 } = {}) {
  return db
    .select({
      id: admin_audit_log.id,
      action: admin_audit_log.action,
      admin_id: admin_audit_log.admin_id,
      target_user_id: admin_audit_log.target_user_id,
      metadata: admin_audit_log.metadata,
      ip_address: admin_audit_log.ip_address,
      created_at: admin_audit_log.created_at,
    })
    .from(admin_audit_log)
    .orderBy(desc(admin_audit_log.created_at))
    .limit(limit)
    .offset(offset)
}

// ── system health ───────────────────────────────────────────────────────────────
export async function getSystemCounts() {
  const tables: Record<string, PgTable> = {
    properties,
    units,
    tenants,
    leases,
    rent_payments,
    maintenance_requests,
    expenses,
    usage_events,
    activity_log,
  }
  const entries = await Promise.all(
    Object.entries(tables).map(async ([name, tbl]) => [name, await countRows(tbl)] as const)
  )
  const cron = await db
    .select({ last: sql<string | null>`max(${follow_up_rules.last_run_at})` })
    .from(follow_up_rules)
  return { counts: Object.fromEntries(entries) as Record<string, number>, cronLastRun: cron[0]?.last ?? null }
}

export function getEnvHealth() {
  const keys = [
    "DATABASE_URL",
    "BETTER_AUTH_SECRET",
    "BETTER_AUTH_URL",
    "STRIPE_SECRET_KEY",
    "STRIPE_WEBHOOK_SECRET",
    "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
    "OPENAI_API_KEY",
    "ANTHROPIC_API_KEY",
    "SMTP_HOST",
    "SMTP_USER",
    "GOOGLE_CLIENT_ID",
    "CRON_SECRET",
    "NEXT_PUBLIC_APP_URL",
    "ADMIN_USER_IDS",
  ]
  return keys.map((key) => ({ key, present: Boolean(process.env[key]) }))
}

// ── users table (server paginated + searchable) ─────────────────────────────────
const USER_SORTS = {
  created_at: profiles.created_at,
  email: profiles.email,
  plan: profiles.plan,
} as const
export type UserSort = keyof typeof USER_SORTS

export async function getUsersPage(opts: {
  q?: string
  page?: number
  pageSize?: number
  plan?: string
  sort?: string
  dir?: "asc" | "desc"
}) {
  const page = Math.max(1, opts.page ?? 1)
  const pageSize = Math.min(100, Math.max(1, opts.pageSize ?? 25))
  const sortKey: UserSort = (opts.sort && opts.sort in USER_SORTS ? opts.sort : "created_at") as UserSort
  const sortCol = USER_SORTS[sortKey]
  const order = opts.dir === "asc" ? asc(sortCol) : desc(sortCol)

  const conds = []
  if (opts.q) {
    const like = `%${opts.q}%`
    conds.push(or(ilike(profiles.email, like), ilike(profiles.full_name, like)))
  }
  if (opts.plan && PLANS.includes(opts.plan as Plan)) {
    conds.push(eq(profiles.plan, opts.plan as Plan))
  }
  const where = conds.length ? and(...conds) : undefined

  const [totalR, rows] = await Promise.all([
    db.select({ c: sql<number>`count(*)::int` }).from(profiles).where(where),
    db
      .select({
        id: profiles.id,
        email: profiles.email,
        full_name: profiles.full_name,
        plan: profiles.plan,
        subscription_status: profiles.subscription_status,
        created_at: profiles.created_at,
        banned: user.banned,
        role: user.role,
        emailVerified: user.emailVerified,
      })
      .from(profiles)
      .leftJoin(user, eq(profiles.id, user.id))
      .where(where)
      .orderBy(order)
      .limit(pageSize)
      .offset((page - 1) * pageSize),
  ])

  const ids = rows.map((r) => r.id)
  const [propC, tenC, lastA] = ids.length
    ? await Promise.all([
        db
          .select({ user_id: properties.user_id, c: sql<number>`count(*)::int` })
          .from(properties)
          .where(inArray(properties.user_id, ids))
          .groupBy(properties.user_id),
        db
          .select({ user_id: tenants.user_id, c: sql<number>`count(*)::int` })
          .from(tenants)
          .where(inArray(tenants.user_id, ids))
          .groupBy(tenants.user_id),
        db
          .select({ user_id: activity_log.user_id, last: sql<string>`max(${activity_log.created_at})` })
          .from(activity_log)
          .where(inArray(activity_log.user_id, ids))
          .groupBy(activity_log.user_id),
      ])
    : [[], [], []]

  const pc = Object.fromEntries(propC.map((r) => [r.user_id, r.c]))
  const tc = Object.fromEntries(tenC.map((r) => [r.user_id, r.c]))
  const la = Object.fromEntries(lastA.map((r) => [r.user_id, r.last]))

  return {
    rows: rows.map((r) => ({
      ...r,
      propertyCount: pc[r.id] ?? 0,
      tenantCount: tc[r.id] ?? 0,
      lastActivity: (la[r.id] as string | undefined) ?? null,
    })),
    total: totalR[0].c,
    page,
    pageSize,
    pageCount: Math.max(1, Math.ceil(totalR[0].c / pageSize)),
  }
}

export type AdminUserRow = Awaited<ReturnType<typeof getUsersPage>>["rows"][number]

// ── single user detail ──────────────────────────────────────────────────────────
export async function getUserDetail(id: string) {
  const profile = await db.query.profiles.findFirst({ where: eq(profiles.id, id) })
  if (!profile) return null
  const account = await db.query.user.findFirst({ where: eq(user.id, id) })

  const tableFor: Record<string, PgTable> = {
    propertyCount: properties,
    unitCount: units,
    tenantCount: tenants,
    leaseCount: leases,
    paymentCount: rent_payments,
    maintenanceCount: maintenance_requests,
    expenseCount: expenses,
    aiCount: usage_events,
  }

  const entries = await Promise.all(
    Object.entries(tableFor).map(async ([key, tbl]) => [key, await countRowsForUser(tbl, id)] as const)
  )

  const recentActivity = await db
    .select({
      id: activity_log.id,
      type: activity_log.type,
      title: activity_log.title,
      created_at: activity_log.created_at,
    })
    .from(activity_log)
    .where(eq(activity_log.user_id, id))
    .orderBy(desc(activity_log.created_at))
    .limit(15)

  return {
    profile,
    account: account ?? null,
    counts: Object.fromEntries(entries) as Record<keyof typeof tableFor, number>,
    recentActivity,
  }
}

// ── CSV helpers (shared by admin export routes) ─────────────────────────────────
export function toCsv(headers: string[], rows: (string | number | null | undefined)[][]) {
  const esc = (v: string | number | null | undefined) => {
    let s = v === null || v === undefined ? "" : String(v)
    // Neutralize spreadsheet formula injection: values that begin with a
    // formula trigger (= + - @) or a control char (tab/CR) are prefixed with a
    // single quote so Excel/Sheets treat them as text, not executable formulas.
    if (/^[=+\-@\t\r]/.test(s)) s = `'${s}`
    return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s
  }
  return [headers, ...rows].map((r) => r.map(esc).join(",")).join("\n")
}
