import "server-only"

import { and, asc, desc, eq, gte, lte, count, like, type SQL, sql } from "drizzle-orm"

import { db } from "@/shared/db"
import { auditLogs, loginLogs, dataChangeLogs } from "@/shared/db/schema"
import type {
  AuditLog,
  AuditLogQueryParams,
  AuditOverviewStats,
  AuditTrendPoint,
  DataChangeActionStat,
  DataChangeLog,
  DataChangeLogQueryParams,
  DataChangeStat,
  LoginLog,
  LoginLogQueryParams,
  PaginatedResult,
} from "./types"

const toIso = (d: Date): string => d.toISOString()

const DEFAULT_PAGE_SIZE = 20
const MAX_PAGE_SIZE = 100

const clampPageSize = (size?: number): number => {
  if (!size || size <= 0) return DEFAULT_PAGE_SIZE
  return Math.min(size, MAX_PAGE_SIZE)
}

const clampPage = (page?: number): number => {
  if (!page || page <= 0) return 1
  return page
}

export async function getAuditLogs(
  params?: AuditLogQueryParams
): Promise<PaginatedResult<AuditLog>> {
  const page = clampPage(params?.page)
  const pageSize = clampPageSize(params?.pageSize)
  const offset = (page - 1) * pageSize

  const conditions = []
  if (params?.userId) conditions.push(eq(auditLogs.userId, params.userId))
  if (params?.module) conditions.push(eq(auditLogs.module, params.module))
  if (params?.action) conditions.push(like(auditLogs.action, `%${params.action}%`))
  if (params?.status) conditions.push(eq(auditLogs.status, params.status))
  if (params?.startDate) conditions.push(gte(auditLogs.createdAt, new Date(params.startDate)))
  if (params?.endDate) conditions.push(lte(auditLogs.createdAt, new Date(params.endDate)))

  const where = conditions.length ? and(...conditions) : undefined

  try {
    const [rows, totalRows] = await Promise.all([
      db
        .select()
        .from(auditLogs)
        .where(where)
        .orderBy(desc(auditLogs.createdAt))
        .limit(pageSize)
        .offset(offset),
      db.select({ value: count() }).from(auditLogs).where(where),
    ])

    const total = Number(totalRows[0]?.value ?? 0)
    return {
      items: rows.map((r) => ({
        id: r.id,
        userId: r.userId,
        userName: r.userName,
        action: r.action,
        module: r.module,
        targetId: r.targetId ?? null,
        targetType: r.targetType ?? null,
        detail: r.detail ?? null,
        ipAddress: r.ipAddress ?? null,
        userAgent: r.userAgent ?? null,
        status: r.status,
        createdAt: toIso(r.createdAt),
      })),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    }
  } catch (error) {
    console.error("getAuditLogs failed:", error)
    throw error
  }
}

export async function getLoginLogs(
  params?: LoginLogQueryParams
): Promise<PaginatedResult<LoginLog>> {
  const page = clampPage(params?.page)
  const pageSize = clampPageSize(params?.pageSize)
  const offset = (page - 1) * pageSize

  const conditions: SQL[] = []
  if (params?.userId) conditions.push(eq(loginLogs.userId, params.userId))
  if (params?.action) conditions.push(eq(loginLogs.action, params.action))
  if (params?.status) conditions.push(eq(loginLogs.status, params.status))
  if (params?.startDate) conditions.push(gte(loginLogs.createdAt, new Date(params.startDate)))
  if (params?.endDate) conditions.push(lte(loginLogs.createdAt, new Date(params.endDate)))

  const where = conditions.length ? and(...conditions) : undefined

  try {
    const [rows, totalRows] = await Promise.all([
      db
        .select()
        .from(loginLogs)
        .where(where)
        .orderBy(desc(loginLogs.createdAt))
        .limit(pageSize)
        .offset(offset),
      db.select({ value: count() }).from(loginLogs).where(where),
    ])

    const total = Number(totalRows[0]?.value ?? 0)
    return {
      items: rows.map((r) => ({
        id: r.id,
        userId: r.userId ?? null,
        userEmail: r.userEmail,
        action: r.action,
        status: r.status,
        ipAddress: r.ipAddress ?? null,
        userAgent: r.userAgent ?? null,
        errorMessage: r.errorMessage ?? null,
        createdAt: toIso(r.createdAt),
      })),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    }
  } catch (error) {
    console.error("getLoginLogs failed:", error)
    throw error
  }
}

export async function getAuditModuleOptions(): Promise<string[]> {
  try {
    const rows = await db
      .selectDistinct({ module: auditLogs.module })
      .from(auditLogs)
      .orderBy(asc(auditLogs.module))
    return rows.map((r) => r.module)
  } catch (error) {
    console.error("getAuditModuleOptions failed:", error)
    throw error
  }
}

export async function getDataChangeLogs(
  params?: DataChangeLogQueryParams
): Promise<PaginatedResult<DataChangeLog>> {
  const page = clampPage(params?.page)
  const pageSize = clampPageSize(params?.pageSize)
  const offset = (page - 1) * pageSize

  const conditions = []
  if (params?.tableName) conditions.push(eq(dataChangeLogs.tableName, params.tableName))
  if (params?.recordId) conditions.push(eq(dataChangeLogs.recordId, params.recordId))
  if (params?.action) conditions.push(eq(dataChangeLogs.action, params.action))
  if (params?.userId) conditions.push(eq(dataChangeLogs.changedBy, params.userId))
  if (params?.startDate) conditions.push(gte(dataChangeLogs.createdAt, new Date(params.startDate)))
  if (params?.endDate) conditions.push(lte(dataChangeLogs.createdAt, new Date(params.endDate)))

  const where = conditions.length ? and(...conditions) : undefined

  try {
    const [rows, totalRows] = await Promise.all([
      db
        .select()
        .from(dataChangeLogs)
        .where(where)
        .orderBy(desc(dataChangeLogs.createdAt))
        .limit(pageSize)
        .offset(offset),
      db.select({ value: count() }).from(dataChangeLogs).where(where),
    ])

    const total = Number(totalRows[0]?.value ?? 0)
    return {
      items: rows.map((r) => ({
        id: r.id,
        tableName: r.tableName,
        recordId: r.recordId,
        action: r.action,
        oldValue: r.oldValue ?? null,
        newValue: r.newValue ?? null,
        changedBy: r.changedBy,
        changedByName: r.changedByName,
        ipAddress: r.ipAddress ?? null,
        createdAt: toIso(r.createdAt),
      })),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    }
  } catch (error) {
    console.error("getDataChangeLogs failed:", error)
    throw error
  }
}

export async function getDataChangeStats(): Promise<DataChangeStat[]> {
  try {
    const rows = await db
      .select({
        tableName: dataChangeLogs.tableName,
        count: count(),
      })
      .from(dataChangeLogs)
      .groupBy(dataChangeLogs.tableName)
      .orderBy(desc(count()))
    return rows.map((r) => ({ tableName: r.tableName, count: Number(r.count) }))
  } catch (error) {
    console.error("getDataChangeStats failed:", error)
    throw error
  }
}

export async function getDataChangeTableOptions(): Promise<string[]> {
  try {
    const rows = await db
      .selectDistinct({ tableName: dataChangeLogs.tableName })
      .from(dataChangeLogs)
      .orderBy(asc(dataChangeLogs.tableName))
    return rows.map((r) => r.tableName)
  } catch (error) {
    console.error("getDataChangeTableOptions failed:", error)
    throw error
  }
}

/**
 * Export-ready: fetch all audit logs matching params (no pagination cap).
 */
export async function getAuditLogsForExport(
  params?: AuditLogQueryParams
): Promise<AuditLog[]> {
  const items: AuditLog[] = []
  let page = 1
  let hasMore = true
  while (hasMore) {
    const result = await getAuditLogs({ ...params, page, pageSize: MAX_PAGE_SIZE })
    items.push(...result.items)
    hasMore = result.items.length === MAX_PAGE_SIZE
    page += 1
  }
  return items
}

/**
 * Export-ready: fetch all login logs matching params (no pagination cap).
 */
export async function getLoginLogsForExport(
  params?: LoginLogQueryParams
): Promise<LoginLog[]> {
  const items: LoginLog[] = []
  let page = 1
  let hasMore = true
  while (hasMore) {
    const result = await getLoginLogs({ ...params, page, pageSize: MAX_PAGE_SIZE })
    items.push(...result.items)
    hasMore = result.items.length === MAX_PAGE_SIZE
    page += 1
  }
  return items
}

/**
 * Export-ready: fetch all data change logs matching params.
 */
export async function getDataChangeLogsForExport(
  params?: DataChangeLogQueryParams
): Promise<DataChangeLog[]> {
  const items: DataChangeLog[] = []
  let page = 1
  let hasMore = true
  while (hasMore) {
    const result = await getDataChangeLogs({ ...params, page, pageSize: MAX_PAGE_SIZE })
    items.push(...result.items)
    hasMore = result.items.length === MAX_PAGE_SIZE
    page += 1
  }
  return items
}

// ─── 概览仪表盘统计 ──────────────────────────────────────────

/** 计算今日 0 点（本地时区）的 Date 对象 */
function startOfToday(): Date {
  const now = new Date()
  return new Date(now.getFullYear(), now.getMonth(), now.getDate())
}

/** 计算最近 N 天的起始日期（0 点） */
function startOfDaysAgo(days: number): Date {
  const now = new Date()
  return new Date(now.getFullYear(), now.getMonth(), now.getDate() - days + 1)
}

/** 将 Date 格式化为 YYYY-MM-DD（本地时区） */
function formatDateKey(d: Date): string {
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, "0")
  const day = String(d.getDate()).padStart(2, "0")
  return `${y}-${m}-${day}`
}

/**
 * 获取审计概览统计（今日数据 + 总数）
 */
export async function getAuditOverviewStats(): Promise<AuditOverviewStats> {
  const todayStart = startOfToday()
  try {
    const [auditToday, failedLoginToday, dataChangeToday, totalAudit] = await Promise.all([
      db.select({ value: count() }).from(auditLogs).where(gte(auditLogs.createdAt, todayStart)),
      db
        .select({ value: count() })
        .from(loginLogs)
        .where(and(gte(loginLogs.createdAt, todayStart), eq(loginLogs.status, "failure"))),
      db
        .select({ value: count() })
        .from(dataChangeLogs)
        .where(gte(dataChangeLogs.createdAt, todayStart)),
      db.select({ value: count() }).from(auditLogs),
    ])

    return {
      auditEventsToday: Number(auditToday[0]?.value ?? 0),
      failedLoginsToday: Number(failedLoginToday[0]?.value ?? 0),
      dataChangesToday: Number(dataChangeToday[0]?.value ?? 0),
      totalAuditLogs: Number(totalAudit[0]?.value ?? 0),
    }
  } catch (error) {
    console.error("getAuditOverviewStats failed:", error)
    throw error
  }
}

/**
 * 获取审计活动趋势（最近 N 天的每日计数）
 *
 * 使用 MySQL `DATE()` 分组聚合，避免 N 次查询。
 */
export async function getAuditTrend(days: number = 7): Promise<AuditTrendPoint[]> {
  const startDate = startOfDaysAgo(days)
  try {
    const [auditRows, loginRows, dataChangeRows] = await Promise.all([
      db
        .select({
          date: sql<string>`DATE(${auditLogs.createdAt})`,
          count: count(),
        })
        .from(auditLogs)
        .where(gte(auditLogs.createdAt, startDate))
        .groupBy(sql`DATE(${auditLogs.createdAt})`),
      db
        .select({
          date: sql<string>`DATE(${loginLogs.createdAt})`,
          count: count(),
        })
        .from(loginLogs)
        .where(gte(loginLogs.createdAt, startDate))
        .groupBy(sql`DATE(${loginLogs.createdAt})`),
      db
        .select({
          date: sql<string>`DATE(${dataChangeLogs.createdAt})`,
          count: count(),
        })
        .from(dataChangeLogs)
        .where(gte(dataChangeLogs.createdAt, startDate))
        .groupBy(sql`DATE(${dataChangeLogs.createdAt})`),
    ])

    // 合并三源数据，填充缺失日期为 0
    const auditMap = new Map(auditRows.map((r) => [r.date, Number(r.count)]))
    const loginMap = new Map(loginRows.map((r) => [r.date, Number(r.count)]))
    const dataChangeMap = new Map(dataChangeRows.map((r) => [r.date, Number(r.count)]))

    const result: AuditTrendPoint[] = []
    for (let i = days - 1; i >= 0; i--) {
      const d = new Date(startDate)
      d.setDate(d.getDate() + (days - 1 - i))
      const key = formatDateKey(d)
      result.push({
        date: key,
        auditEvents: auditMap.get(key) ?? 0,
        loginEvents: loginMap.get(key) ?? 0,
        dataChanges: dataChangeMap.get(key) ?? 0,
      })
    }
    return result
  } catch (error) {
    console.error("getAuditTrend failed:", error)
    throw error
  }
}

/**
 * 获取数据变更按动作统计（create/update/delete）
 */
export async function getDataChangeActionStats(): Promise<DataChangeActionStat[]> {
  try {
    const rows = await db
      .select({
        action: dataChangeLogs.action,
        count: count(),
      })
      .from(dataChangeLogs)
      .groupBy(dataChangeLogs.action)
    return rows.map((r) => ({ action: r.action, count: Number(r.count) }))
  } catch (error) {
    console.error("getDataChangeActionStats failed:", error)
    throw error
  }
}
