import {
  pgTable,
  text,
  timestamp,
  boolean,
  integer,
  jsonb,
  pgEnum,
  primaryKey,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";

// Mirrors recipes.ts's DietaryTags shape — duplicated rather than imported to
// avoid a users.ts <-> recipes.ts circular import (recipes.ts already
// references users.ts for the authorId FK).
export type UserDietaryTags = {
  vegan?: boolean;
  vegetarian?: boolean;
  glutenFree?: boolean;
  dairyFree?: boolean;
  nutFree?: boolean;
  halal?: boolean;
  kosher?: boolean;
};

export const userRoleEnum = pgEnum("user_role", ["user", "moderator", "admin"]);
export const tierEnum = pgEnum("tier", ["free", "pro", "family"]);
export const unitPrefEnum = pgEnum("unit_pref", ["metric", "imperial"]);
export const apiKeyScopeEnum = pgEnum("api_key_scope", ["full", "read"]);
export const subscriptionStatusEnum = pgEnum("subscription_status", ["active", "trialing", "past_due", "canceled", "incomplete"]);

export const users = pgTable("users", {
  id: text("id").primaryKey(),
  email: text("email").notNull().unique(),
  emailVerified: boolean("email_verified").notNull().default(false),
  name: text("name").notNull(),
  avatarUrl: text("avatar_url"),
  hasCustomAvatar: boolean("has_custom_avatar").notNull().default(false),
  avatarSizeMb: integer("avatar_size_mb").notNull().default(0),
  // Off by default — Gravatar is looked up by an MD5 hash of the user's
  // email, sent to a third party (gravatar.com), which some users won't want
  // regardless of MD5 being effectively reversible for a known email.
  useGravatar: boolean("use_gravatar").notNull().default(false),
  bio: text("bio"),
  privateBio: text("private_bio"),
  isPrivate: boolean("is_private").notNull().default(false),
  username: text("username").unique(),
  role: userRoleEnum("role").notNull().default("user"),
  tier: tierEnum("tier").notNull().default("free"),
  // Orthogonal to both role (staff hierarchy) and tier (billing plan) —
  // gates webhooks and self-serve API keys. Admin-settable always; also
  // self-serve toggleable by the user themselves once on a paid tier (no
  // added fee) via PATCH /api/v1/users/me/developer-access — see
  // hasDeveloperAccess() in lib/permissions.ts.
  isDeveloper: boolean("is_developer").notNull().default(false),
  // Separate from isDeveloper on purpose — BYOK means routing real AI
  // provider spend through Epicure on the user's own key, which warrants a
  // manual admin check-in rather than blanket self-serve. Admin-only, no
  // self-serve path. See hasByokAccess() in lib/permissions.ts.
  isByokEnabled: boolean("is_byok_enabled").notNull().default(false),
  stripeCustomerId: text("stripe_customer_id").unique(),
  stripeSubscriptionId: text("stripe_subscription_id"),
  // Only the webhook handler (server-side, signature-verified) writes
  // these three — never trust a client to set its own tier. past_due
  // deliberately does NOT downgrade tier on its own (Stripe retries the
  // card automatically); only customer.subscription.deleted does.
  subscriptionStatus: subscriptionStatusEnum("subscription_status"),
  currentPeriodEnd: timestamp("current_period_end"),
  unitPref: unitPrefEnum("unit_pref").notNull().default("metric"),
  locale: text("locale").notNull().default("en"),
  twoFactorEnabled: boolean("two_factor_enabled").notNull().default(false),
  // Null = hasn't been through (or finished/skipped) the post-signup
  // onboarding wizard yet. Set once, either on completion or on skip — both
  // count as "done," so the wizard never shows twice.
  onboardingCompletedAt: timestamp("onboarding_completed_at"),
  dietaryTags: jsonb("dietary_tags").$type<UserDietaryTags>().default({}),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

// Better Auth requires these tables
export const sessions = pgTable("sessions", {
  id: text("id").primaryKey(),
  expiresAt: timestamp("expires_at").notNull(),
  token: text("token").notNull().unique(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
  ipAddress: text("ip_address"),
  userAgent: text("user_agent"),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
});

export const accounts = pgTable("accounts", {
  id: text("id").primaryKey(),
  accountId: text("account_id").notNull(),
  providerId: text("provider_id").notNull(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  accessToken: text("access_token"),
  refreshToken: text("refresh_token"),
  idToken: text("id_token"),
  accessTokenExpiresAt: timestamp("access_token_expires_at"),
  refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
  scope: text("scope"),
  password: text("password"),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const verifications = pgTable("verifications", {
  id: text("id").primaryKey(),
  identifier: text("identifier").notNull(),
  value: text("value").notNull(),
  expiresAt: timestamp("expires_at").notNull(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

// Better Auth's twoFactor plugin table — secret/backupCodes are encrypted by
// the plugin before storage, not plaintext.
export const twoFactors = pgTable("two_factors", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  secret: text("secret").notNull(),
  backupCodes: text("backup_codes").notNull(),
  verified: boolean("verified").notNull().default(true),
});

export const userFollows = pgTable("user_follows", {
  followerId: text("follower_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  followingId: text("following_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
  primaryKey({ columns: [t.followerId, t.followingId] }),
]);

export const userBlocks = pgTable("user_blocks", {
  blockerId: text("blocker_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  blockedId: text("blocked_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
  primaryKey({ columns: [t.blockerId, t.blockedId] }),
]);

export const userBlocksRelations = relations(userBlocks, ({ one }) => ({
  blocker: one(users, { fields: [userBlocks.blockerId], references: [users.id], relationName: "blocker" }),
  blocked: one(users, { fields: [userBlocks.blockedId], references: [users.id], relationName: "blocked" }),
}));

export const apiKeys = pgTable("api_keys", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  name: text("name").notNull(),
  keyHash: text("key_hash").notNull().unique(),
  scope: apiKeyScopeEnum("scope").notNull().default("full"),
  lastUsedAt: timestamp("last_used_at"),
  createdAt: timestamp("created_at").notNull().defaultNow(),
});

export const userAiKeys = pgTable("user_ai_keys", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  provider: text("provider").notNull(),
  encryptedKey: text("encrypted_key").notNull(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
});

export const usersRelations = relations(users, ({ many }) => ({
  sessions: many(sessions),
  accounts: many(accounts),
  followers: many(userFollows, { relationName: "following" }),
  following: many(userFollows, { relationName: "follower" }),
  apiKeys: many(apiKeys),
  aiKeys: many(userAiKeys),
}));

export const userAiKeysRelations = relations(userAiKeys, ({ one }) => ({
  user: one(users, { fields: [userAiKeys.userId], references: [users.id] }),
}));

export const userFollowsRelations = relations(userFollows, ({ one }) => ({
  follower: one(users, { fields: [userFollows.followerId], references: [users.id], relationName: "follower" }),
  following: one(users, { fields: [userFollows.followingId], references: [users.id], relationName: "following" }),
}));

export const pushSubscriptions = pgTable("push_subscriptions", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  endpoint: text("endpoint").notNull().unique(),
  p256dh: text("p256dh").notNull(),
  auth: text("auth").notNull(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
});

export const userNutritionGoals = pgTable("user_nutrition_goals", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(),
  caloriesKcal: integer("calories_kcal"),
  proteinG: integer("protein_g"),
  carbsG: integer("carbs_g"),
  fatG: integer("fat_g"),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const userNotificationPrefs = pgTable("user_notification_prefs", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(),
  follow: boolean("follow").notNull().default(true),
  comment: boolean("comment").notNull().default(true),
  reply: boolean("reply").notNull().default(true),
  reaction: boolean("reaction").notNull().default(true),
  rating: boolean("rating").notNull().default(true),
  mention: boolean("mention").notNull().default(true),
  leftoverExpiring: boolean("leftover_expiring").notNull().default(true),
  shoppingList: boolean("shopping_list").notNull().default(true),
  // Email variants of the same categories — independent from the push
  // toggles above, so a user can get push but not email (or vice versa).
  followEmail: boolean("follow_email").notNull().default(true),
  commentEmail: boolean("comment_email").notNull().default(true),
  replyEmail: boolean("reply_email").notNull().default(true),
  reactionEmail: boolean("reaction_email").notNull().default(true),
  ratingEmail: boolean("rating_email").notNull().default(true),
  mentionEmail: boolean("mention_email").notNull().default(true),
  leftoverExpiringEmail: boolean("leftover_expiring_email").notNull().default(true),
  shoppingListEmail: boolean("shopping_list_email").notNull().default(true),
  weeklyDigestEmail: boolean("weekly_digest_email").notNull().default(true),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const pushSubscriptionsRelations = relations(pushSubscriptions, ({ one }) => ({
  user: one(users, { fields: [pushSubscriptions.userId], references: [users.id] }),
}));

export const userNotificationPrefsRelations = relations(userNotificationPrefs, ({ one }) => ({
  user: one(users, { fields: [userNotificationPrefs.userId], references: [users.id] }),
}));

// Lets a user hide optional features from their own nav — purely cosmetic
// declutter, not an access restriction: the underlying pages/routes stay
// reachable by direct URL even when hidden here.
export const userFeaturePrefs = pgTable("user_feature_prefs", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(),
  nutrition: boolean("nutrition").notNull().default(true),
  pantry: boolean("pantry").notNull().default(true),
  mealPlan: boolean("meal_plan").notNull().default(true),
  shoppingLists: boolean("shopping_lists").notNull().default(true),
  collections: boolean("collections").notNull().default(true),
  messages: boolean("messages").notNull().default(true),
  chatbots: boolean("chatbots").notNull().default(true),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const userFeaturePrefsRelations = relations(userFeaturePrefs, ({ one }) => ({
  user: one(users, { fields: [userFeaturePrefs.userId], references: [users.id] }),
}));

export const userNutritionGoalsRelations = relations(userNutritionGoals, ({ one }) => ({
  user: one(users, { fields: [userNutritionGoals.userId], references: [users.id] }),
}));

export const userModelPrefs = pgTable("user_model_prefs", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(),
  textProvider: text("text_provider"),
  textModel: text("text_model"),
  visionProvider: text("vision_provider"),
  visionModel: text("vision_model"),
  mealPlanProvider: text("meal_plan_provider"),
  mealPlanModel: text("meal_plan_model"),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const userModelPrefsRelations = relations(userModelPrefs, ({ one }) => ({
  user: one(users, { fields: [userModelPrefs.userId], references: [users.id] }),
}));
