import { useDB, eq, asc, sql, and } from '../../utils/database'
import { user, account, subscriptions, prices, products } from '../schema'
import { calculateOffset, countRecords } from './utils'

export interface UpdateUserPayload {
  name: string
  image?: string | null
  preferredLanguage?: string | null
  timezone?: string | null
}

export interface UserRecord {
  id: string
  name: string
  email: string
  emailVerified: boolean
  superAdmin: boolean
  proAccount: boolean
  banned: boolean
  bannedReason: string | null
  bannedUntil: Date | null
  image: string | null
  preferredLanguage: string | null
  timezone: string | null
  createdAt: Date
  updatedAt: Date
}

export const getUserById = async (id: string): Promise<UserRecord | undefined> => {
  try {
    const db = useDB()
    const found = await db
      .select()
      .from(user)
      .where(eq(user.id, id))
      .limit(1)

    return (found?.[0] as UserRecord) || undefined
  } catch (error) {
    console.error('[getUserById]', id, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to get user by ID'
    })
  }
}

export const updateUserProfile = async (
  id: string,
  payload: UpdateUserPayload
): Promise<UserRecord | undefined> => {
  try {
    const db = useDB()

    const setValues: Record<string, unknown> = {
      name: payload.name,
      image: payload.image ?? null,
      updatedAt: new Date()
    }
    if (payload.preferredLanguage !== undefined) setValues.preferredLanguage = payload.preferredLanguage
    if (payload.timezone !== undefined) setValues.timezone = payload.timezone

    const [updated] = await db
      .update(user)
      .set(setValues)
      .where(eq(user.id, id))
      .returning()

    return updated as UserRecord | undefined
  } catch (error) {
    console.error('[updateUserProfile]', id, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to update user profile'
    })
  }
}

export const deleteUserById = async (id: string): Promise<void> => {
  try {
    const db = useDB()
    await db.delete(user).where(eq(user.id, id))
  } catch (error) {
    console.error('[deleteUserById]', id, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to delete user'
    })
  }
}

// Generic updater used by services/webhooks to toggle flags like proAccount
export interface UpdateUserFlagsPayload {
  name?: string
  image?: string | null
  proAccount?: boolean
  superAdmin?: boolean
  emailVerified?: boolean
  banned?: boolean
  bannedReason?: string | null
  bannedUntil?: Date | null
  preferredLanguage?: string | null
  timezone?: string | null
}

export const updateUser = async (
  id: string,
  payload: UpdateUserFlagsPayload
): Promise<UserRecord | undefined> => {
  try {
    const db = useDB()

    const setValues: Record<string, unknown> = {}
    if (payload.name !== undefined) setValues.name = payload.name
    if (payload.image !== undefined) setValues.image = payload.image
    if (payload.proAccount !== undefined) setValues.proAccount = payload.proAccount
    if (payload.superAdmin !== undefined) setValues.superAdmin = payload.superAdmin
    if (payload.emailVerified !== undefined) setValues.emailVerified = payload.emailVerified
    if (payload.banned !== undefined) setValues.banned = payload.banned
    if (payload.bannedReason !== undefined) setValues.bannedReason = payload.bannedReason
    if (payload.bannedUntil !== undefined) setValues.bannedUntil = payload.bannedUntil
    if (payload.preferredLanguage !== undefined) setValues.preferredLanguage = payload.preferredLanguage
    if (payload.timezone !== undefined) setValues.timezone = payload.timezone
    setValues.updatedAt = new Date()

    const [updated] = await db
      .update(user)
      .set(setValues)
      .where(eq(user.id, id))
      .returning()

    return updated as UserRecord | undefined
  } catch (error) {
    console.error('[updateUser]', id, payload, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to update user'
    })
  }
}

// Update user preferences (language and timezone)
export interface UpdateUserPreferencesPayload {
  preferredLanguage?: string | null
  timezone?: string | null
}

export const updateUserPreferences = async (
  id: string,
  payload: UpdateUserPreferencesPayload
): Promise<UserRecord | undefined> => {
  try {
    const db = useDB()

    const setValues: Record<string, unknown> = { updatedAt: new Date() }
    if (payload.preferredLanguage !== undefined) setValues.preferredLanguage = payload.preferredLanguage
    if (payload.timezone !== undefined) setValues.timezone = payload.timezone

    const [updated] = await db
      .update(user)
      .set(setValues)
      .where(eq(user.id, id))
      .returning()

    return updated as UserRecord | undefined
  } catch (error) {
    console.error('[updateUserPreferences]', id, payload, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to update user preferences'
    })
  }
}

export interface CreateUserPayload {
  id?: string
  email: string
  name: string
}

export const createUser = async (payload: CreateUserPayload): Promise<string> => {
  try {
    const db = useDB()
    const userId = payload.id ?? crypto.randomUUID()
    const result = await db
      .insert(user)
      .values({
        id: userId,
        email: payload.email,
        name: payload.name
      })
      .returning({ id: user.id, name: user.name, email: user.email, image: user.image })

    const createdUser = result[0]
    if (!createdUser?.id) {
      throw new Error('Insert returned no id')
    }

    // Notify super admins about the new user (non-blocking)
    import('../../utils/user-notifications').then(({ notifySuperAdminsOfNewUser }) => {
      notifySuperAdminsOfNewUser({
        id: createdUser.id,
        name: createdUser.name,
        email: createdUser.email,
        image: createdUser.image
      }).catch((error: unknown) => {
        console.error('[createUser] Failed to notify super admins:', error)
      })
    })

    return createdUser.id
  } catch (error) {
    console.error('[createUser]', payload.email, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to create user'
    })
  }
}

export interface ListUsersOptions {
  page: number
  pageSize: number
  role?: 'all' | 'admin' | 'user'
  emailVerified?: 'all' | 'verified' | 'unverified'
  banned?: 'all' | 'banned' | 'active'
  subscription?: string // 'all' | 'active' | 'none' | productId | comma-separated productIds
  provider?: 'all' | 'credential' | 'github' | 'google'
}

export interface ListUsersResult {
  users: Array<Pick<
    UserRecord,
    'id' | 'email' | 'name' | 'emailVerified' | 'createdAt' | 'updatedAt' | 'image' | 'superAdmin' | 'banned' | 'bannedUntil'
  > & {
    providers: string[]
    subscriptions?: Array<{
      id: string
      status: string
      productName: string | null
      productId: string | null
      interval: string | null
      intervalCount: number | null
    }>
  }>
  total: number
}

export const listUsersWithTotal = async (
  options: ListUsersOptions
): Promise<ListUsersResult> => {
  try {
    const db = useDB()

    // Use pagination utilities
    const params = {
      page: options.page,
      pageSize: options.pageSize
    }
    const offset = calculateOffset(params)

    // Build WHERE conditions based on filters
    const conditions = []

    if (options.role === 'admin') {
      conditions.push(eq(user.superAdmin, true))
    } else if (options.role === 'user') {
      conditions.push(eq(user.superAdmin, false))
    }

    if (options.emailVerified === 'verified') {
      conditions.push(eq(user.emailVerified, true))
    } else if (options.emailVerified === 'unverified') {
      conditions.push(eq(user.emailVerified, false))
    }

    if (options.banned === 'banned') {
      conditions.push(eq(user.banned, true))
    } else if (options.banned === 'active') {
      conditions.push(eq(user.banned, false))
    }

    // Build the where clause
    const whereClause = conditions.length > 0 ? and(...conditions) : undefined

    // Count with filters using utility
    const total = await countRecords(db, user, whereClause)

    // Query with left join and group by to aggregate providers
    const usersQueryBase = db
      .select({
        id: user.id,
        email: user.email,
        name: user.name,
        emailVerified: user.emailVerified,
        createdAt: user.createdAt,
        updatedAt: user.updatedAt,
        image: user.image,
        superAdmin: user.superAdmin,
        banned: user.banned,
        bannedUntil: user.bannedUntil,
        // Aggregate provider IDs into an array
        providers: sql<string[]>`COALESCE(array_agg(DISTINCT ${account.providerId}) FILTER (WHERE ${account.providerId} IS NOT NULL), ARRAY[]::text[])`
      })
      .from(user)
      .leftJoin(account, eq(user.id, account.userId))

    const usersPage = await (whereClause
      ? usersQueryBase.where(whereClause)
      : usersQueryBase)
      .groupBy(
        user.id,
        user.email,
        user.name,
        user.emailVerified,
        user.createdAt,
        user.updatedAt,
        user.image,
        user.superAdmin,
        user.banned,
        user.bannedUntil
      )
      .orderBy(asc(user.createdAt))
      .limit(params.pageSize)
      .offset(offset)

    // Fetch subscriptions for all users in the page
    const userIds = usersPage.map(u => u.id)
    const userSubscriptions = await db
      .select({
        userId: subscriptions.userId,
        id: subscriptions.id,
        status: subscriptions.status,
        productName: products.name,
        productId: products.id,
        interval: prices.interval,
        intervalCount: prices.intervalCount
      })
      .from(subscriptions)
      .leftJoin(prices, eq(subscriptions.priceId, prices.id))
      .leftJoin(products, eq(prices.productId, products.id))
      .where(sql`${subscriptions.userId} IN ${userIds}`)

    // Group subscriptions by userId
    const subscriptionsByUser = new Map<string, Array<{
      id: string
      status: string
      productName: string | null
      productId: string | null
      interval: string | null
      intervalCount: number | null
    }>>()

    for (const sub of userSubscriptions) {
      if (!sub.userId) continue
      if (!subscriptionsByUser.has(sub.userId)) {
        subscriptionsByUser.set(sub.userId, [])
      }
      subscriptionsByUser.get(sub.userId)!.push({
        id: sub.id,
        status: sub.status,
        productName: sub.productName,
        productId: sub.productId,
        interval: sub.interval,
        intervalCount: sub.intervalCount
      })
    }

    // Attach subscriptions to users
    let usersWithSubscriptions = usersPage.map(u => ({
      ...u,
      subscriptions: subscriptionsByUser.get(u.id) || []
    }))

    // Apply subscription filter (client-side filtering after fetching subscriptions)
    if (options.subscription === 'active') {
      usersWithSubscriptions = usersWithSubscriptions.filter(u =>
        u.subscriptions && u.subscriptions.length > 0
        && u.subscriptions.some(s => s.status === 'active' || s.status === 'trialing')
      )
    } else if (options.subscription === 'none') {
      usersWithSubscriptions = usersWithSubscriptions.filter(u =>
        !u.subscriptions || u.subscriptions.length === 0
      )
    } else if (options.subscription && options.subscription !== 'all') {
      // Filter by specific product ID(s) - supports comma-separated IDs for products with same name
      const productIds = options.subscription.split(',')
      usersWithSubscriptions = usersWithSubscriptions.filter(u =>
        u.subscriptions && u.subscriptions.length > 0
        && u.subscriptions.some(s =>
          (s.status === 'active' || s.status === 'trialing')
          && s.productId
          && productIds.includes(s.productId)
        )
      )
    }

    // Apply provider filter (client-side filtering after aggregating providers)
    if (options.provider && options.provider !== 'all') {
      usersWithSubscriptions = usersWithSubscriptions.filter(u =>
        u.providers && u.providers.includes(options.provider!)
      )
    }

    return { users: usersWithSubscriptions as ListUsersResult['users'], total }
  } catch (error) {
    console.error('[listUsersWithTotal]', options, error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to list users'
    })
  }
}

export const getSuperAdminUsers = async (): Promise<Pick<UserRecord, 'id' | 'name' | 'email' | 'image'>[]> => {
  try {
    const db = useDB()
    const superAdmins = await db
      .select({
        id: user.id,
        name: user.name,
        email: user.email,
        image: user.image
      })
      .from(user)
      .where(eq(user.superAdmin, true))

    return superAdmins
  } catch (error) {
    console.error('[getSuperAdminUsers]', error)
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to get super admin users'
    })
  }
}
