import {
  pgTable,
  text,
  timestamp,
  varchar,
  numeric,
  jsonb,
  boolean,
  integer,
  index,
  uniqueIndex,
} from "drizzle-orm/pg-core";
import { relations, type InferSelectModel, type InferInsertModel } from "drizzle-orm";

export const users = pgTable(
  "users",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    email: varchar("email", { length: 255 }),
    /**
     * Normalised form of `email` used as the uniqueness key — lower-cased, with
     * provider-specific aliasing removed (see `normaliseEmail`). This is what
     * stops one person from registering the same inbox twenty times over.
     */
    emailKey: varchar("email_key", { length: 255 }),
    name: varchar("name", { length: 160 }),
    /** scrypt digest; null for guests and for accounts created via Google only. */
    passwordHash: varchar("password_hash", { length: 255 }),
    /** Null until the confirmation link is clicked (or Google vouches for it). */
    emailVerifiedAt: timestamp("email_verified_at"),
    isGuest: boolean("is_guest").default(true).notNull(),
    plan: varchar("plan", { length: 32 }).default("free").notNull(), // 'free', 'weekly', 'annual', 'lifetime'
    stripeCustomerId: varchar("stripe_customer_id", { length: 128 }),
    stripeSubscriptionId: varchar("stripe_subscription_id", { length: 128 }),
    /**
     * True once the user has requested cancellation but Stripe has the
     * subscription set to `cancel_at_period_end` — plan/expiresAt stay as-is
     * so Pro access continues until `expiresAt`, then the webhook flips
     * `plan` back to "free" and resets this to false.
     */
    cancelAtPeriodEnd: boolean("cancel_at_period_end").default(false).notNull(),
    scanCount: numeric("scan_count").default("0").notNull(),
    /** Current billing month (`YYYY-MM`) the scan counter belongs to. */
    scanPeriod: varchar("scan_period", { length: 7 }),
    expiresAt: timestamp("expires_at"),
    // Onboarding data for admin analytics
    company: varchar("company", { length: 255 }),
    useCase: varchar("use_case", { length: 100 }), // 'freelancer', 'small_business', 'accountant', 'personal'
    monthlyVolume: varchar("monthly_volume", { length: 50 }), // 'tier_1_10', 'tier_10_50', 'tier_50_150', 'tier_150_plus'
    exportFormat: varchar("export_format", { length: 100 }), // 'xlsx', 'csv', 'pdf', 'google_sheets'
    mainPainPoint: varchar("main_pain_point", { length: 100 }), // 'tax_time', 'lost_receipts', 'manual_typing', 'accountant_requests'
    referralSource: varchar("referral_source", { length: 100 }), // 'google', 'social', 'recommendation', 'article_blog', 'community'
    onboardingCompletedAt: timestamp("onboarding_completed_at"),
    /**
     * True when this registration was one of the first N launch signups and
     * therefore received the one-time 100 free scan bonus.
     */
    launchBonus: boolean("launch_bonus").default(false).notNull(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_users_email").on(table.email),
    index("idx_users_guest_created").on(table.isGuest, table.createdAt),
    // Postgres allows many NULLs in a unique index, so guest rows (no email)
    // stay unaffected while every real account is forced to be one of a kind.
    uniqueIndex("uq_users_email_key").on(table.emailKey),
  ]
);

/**
 * Server-side sessions. The cookie carries a random token; only its SHA-256
 * digest is stored here, so a database leak cannot be replayed as a login.
 */
export const sessions = pgTable(
  "sessions",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    expiresAt: timestamp("expires_at").notNull(),
    userAgent: varchar("user_agent", { length: 255 }),
    ipHash: varchar("ip_hash", { length: 64 }),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_sessions_user_id").on(table.userId),
    index("idx_sessions_expires_at").on(table.expiresAt),
  ]
);

/** Links a local user to an external identity provider (currently Google). */
export const oauth_accounts = pgTable(
  "oauth_accounts",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    provider: varchar("provider", { length: 32 }).notNull(),
    providerAccountId: varchar("provider_account_id", { length: 255 }).notNull(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    uniqueIndex("uq_oauth_provider_account").on(table.provider, table.providerAccountId),
    index("idx_oauth_accounts_user_id").on(table.userId),
  ]
);

/**
 * Single-use password reset links. Same shape and guarantees as the
 * verification tokens, kept in its own table so the two lifecycles (24h
 * confirmation versus 1h reset) never share a row or an expiry policy.
 */
export const password_reset_tokens = pgTable(
  "password_reset_tokens",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    expiresAt: timestamp("expires_at").notNull(),
    consumedAt: timestamp("consumed_at"),
    requestedIpHash: varchar("requested_ip_hash", { length: 64 }),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_password_reset_user_id").on(table.userId),
    index("idx_password_reset_expires_at").on(table.expiresAt),
  ]
);

/** Single-use confirmation links. Stored hashed, expiring, and consumable once. */
export const email_verification_tokens = pgTable(
  "email_verification_tokens",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    email: varchar("email", { length: 255 }).notNull(),
    expiresAt: timestamp("expires_at").notNull(),
    consumedAt: timestamp("consumed_at"),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_email_verification_user_id").on(table.userId),
    index("idx_email_verification_expires_at").on(table.expiresAt),
  ]
);

/** User-defined folders for organizing receipts. Pro-only to create/rename/delete. */
export const projects = pgTable(
  "projects",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    name: varchar("name", { length: 120 }).notNull(),
    /** Fixed palette key (e.g. "blue", "amber") rather than a free hex value. */
    color: varchar("color", { length: 16 }),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_projects_user_id").on(table.userId),
    index("idx_projects_user_created").on(table.userId, table.createdAt),
    uniqueIndex("uq_projects_user_name").on(table.userId, table.name),
  ]
);

export const receipts = pgTable(
  "receipts",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    imageHash: varchar("image_hash", { length: 128 }).notNull(),
    projectId: varchar("project_id", { length: 64 }).references(() => projects.id, {
      onDelete: "set null",
    }),
    storageUrl: text("storage_url"),
    merchantName: varchar("merchant_name", { length: 255 }),
    receiptDate: varchar("receipt_date", { length: 32 }), // YYYY-MM-DD
    receiptNumber: varchar("receipt_number", { length: 128 }),
    documentType: varchar("document_type", { length: 64 }).default("KASSENBON"),
    category: varchar("category", { length: 64 }).default("Sonstiges"),
    currency: varchar("currency", { length: 8 }).default("EUR").notNull(),
    totalAmount: numeric("total_amount", { precision: 12, scale: 2 }).notNull(),
    netAmount: numeric("net_amount", { precision: 12, scale: 2 }),
    tax7Amount: numeric("tax_7_amount", { precision: 12, scale: 2 }),
    tax19Amount: numeric("tax_19_amount", { precision: 12, scale: 2 }),
    tipAmount: numeric("tip_amount", { precision: 12, scale: 2 }),
    taxBreakdownJson: jsonb("tax_breakdown_json"),
    lineItemsJson: jsonb("line_items_json"),
    validationJson: jsonb("validation_json"),
    /**
     * Full extraction payload (merchant address/taxId, time, hospitality,
     * confidence, original filename, …). Indexed columns stay for filters;
     * this blob is the source of truth on GET.
     */
    extractionJson: jsonb("extraction_json"),
    rawOcrText: text("raw_ocr_text"),
    paymentMethod: varchar("payment_method", { length: 64 }),
    isMathValid: boolean("is_math_valid").default(true).notNull(),
    needsReview: boolean("needs_review").default(false).notNull(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_receipts_user_date").on(table.userId, table.receiptDate),
    index("idx_receipts_hash").on(table.imageHash),
    index("idx_receipts_project").on(table.userId, table.projectId),
    index("idx_receipts_duplicate_check").on(
      table.userId,
      table.merchantName,
      table.totalAmount,
      table.receiptDate
    ),
  ]
);

export const line_items = pgTable(
  "line_items",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    receiptId: varchar("receipt_id", { length: 64 })
      .references(() => receipts.id, { onDelete: "cascade" })
      .notNull(),
    description: text("description").notNull(),
    quantity: numeric("quantity", { precision: 10, scale: 3 }).default("1").notNull(),
    price: numeric("price", { precision: 12, scale: 2 }).notNull(),
    unitPrice: numeric("unit_price", { precision: 12, scale: 2 }),
    taxRate: numeric("tax_rate", { precision: 5, scale: 2 }),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_line_items_receipt_id").on(table.receiptId),
  ]
);

export const licenses = pgTable(
  "licenses",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "set null" }),
    licenseKey: varchar("license_key", { length: 128 }).notNull().unique(),
    plan: varchar("plan", { length: 32 }).notNull(), // 'weekly', 'annual', 'lifetime'
    status: varchar("status", { length: 32 }).default("active").notNull(), // 'active', 'expired', 'cancelled'
    stripeCustomerId: varchar("stripe_customer_id", { length: 128 }),
    stripeSubscriptionId: varchar("stripe_subscription_id", { length: 128 }),
    stripeCheckoutSessionId: varchar("stripe_checkout_session_id", { length: 128 }),
    activatedAt: timestamp("activated_at").defaultNow().notNull(),
    expiresAt: timestamp("expires_at"),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_licenses_key").on(table.licenseKey),
    index("idx_licenses_user_id").on(table.userId),
    index("idx_licenses_sub_id").on(table.stripeSubscriptionId),
  ]
);

export const guest_sessions = pgTable(
  "guest_sessions",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    ipHash: varchar("ip_hash", { length: 64 }),
    scanCount: numeric("scan_count").default("0").notNull(),
    lastScanAt: timestamp("last_scan_at"),
    expiresAt: timestamp("expires_at"),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_guest_sessions_ip").on(table.ipHash),
  ]
);

/** Waitlist for mobile app launch. Collected from the landing page. */
export const waitlist = pgTable(
  "waitlist",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    email: varchar("email", { length: 255 }).notNull(),
    name: varchar("name", { length: 160 }),
    source: varchar("source", { length: 100 }).default("landing_page"),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    uniqueIndex("uq_waitlist_email").on(table.email),
    index("idx_waitlist_created_at").on(table.createdAt),
  ]
);

/**
 * Grants of the launch special ("first 500 registrations get 100 free scans").
 * The `position` comes from a Postgres sequence, so concurrent signups can
 * never double-assign the same slot; the count guard caps the special at 500.
 */
export const launch_claims = pgTable(
  "launch_claims",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    userId: varchar("user_id", { length: 64 })
      .references(() => users.id, { onDelete: "cascade" })
      .notNull(),
    position: integer("position").notNull(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    uniqueIndex("uq_launch_claims_user_id").on(table.userId),
    uniqueIndex("uq_launch_claims_position").on(table.position),
  ]
);

/**
 * Immutable security audit log. Every row is an append-only record of a
 * security-relevant event (failed login, password change, admin action, …).
 *
 * PII discipline: the raw IP address is never stored — only its SHA-256 digest,
 * so the table stays usable for correlation without becoming a personal-data
 * goldmine. `email` is kept because the whole point is to see which account was
 * targeted or acted on; it is the same address the user already provided to the
 * app. `metadata` carries event-specific extras (endpoint, attempt counts, …).
 */
export const security_events = pgTable(
  "security_events",
  {
    id: varchar("id", { length: 64 }).primaryKey(),
    /** Stable event type, e.g. `login.failed`, `password.changed`, `admin.action`. */
    type: varchar("type", { length: 64 }).notNull(),
    userId: varchar("user_id", { length: 64 }).references(() => users.id, {
      onDelete: "set null",
    }),
    /** Normalised email of the affected or targeted account, when known. */
    email: varchar("email", { length: 255 }),
    ipHash: varchar("ip_hash", { length: 64 }),
    userAgent: varchar("user_agent", { length: 255 }),
    /** Free-form event details as JSON. */
    metadataJson: jsonb("metadata_json"),
    createdAt: timestamp("created_at").defaultNow().notNull(),
  },
  (table) => [
    index("idx_security_events_type_created").on(table.type, table.createdAt),
    index("idx_security_events_user_created").on(table.userId, table.createdAt),
    index("idx_security_events_ip_created").on(table.ipHash, table.createdAt),
  ]
);

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

// Drizzle Relations
export const usersRelations = relations(users, ({ many }) => ({
  receipts: many(receipts),
  projects: many(projects),
  licenses: many(licenses),
  sessions: many(sessions),
  oauthAccounts: many(oauth_accounts),
  launchClaims: many(launch_claims),
  securityEvents: many(security_events),
}));

export const projectsRelations = relations(projects, ({ one, many }) => ({
  user: one(users, {
    fields: [projects.userId],
    references: [users.id],
  }),
  receipts: many(receipts),
}));

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

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

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

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

export const receiptsRelations = relations(receipts, ({ one, many }) => ({
  user: one(users, {
    fields: [receipts.userId],
    references: [users.id],
  }),
  project: one(projects, {
    fields: [receipts.projectId],
    references: [projects.id],
  }),
  lineItems: many(line_items),
}));

export const lineItemsRelations = relations(line_items, ({ one }) => ({
  receipt: one(receipts, {
    fields: [line_items.receiptId],
    references: [receipts.id],
  }),
}));

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

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

// Inferred Types
export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;

export type Receipt = InferSelectModel<typeof receipts>;
export type NewReceipt = InferInsertModel<typeof receipts>;

export type Project = InferSelectModel<typeof projects>;
export type NewProject = InferInsertModel<typeof projects>;

export type LineItemRecord = InferSelectModel<typeof line_items>;
export type NewLineItemRecord = InferInsertModel<typeof line_items>;

export type License = InferSelectModel<typeof licenses>;
export type NewLicense = InferInsertModel<typeof licenses>;

export type GuestSession = InferSelectModel<typeof guest_sessions>;
export type NewGuestSession = InferInsertModel<typeof guest_sessions>;

export type Session = InferSelectModel<typeof sessions>;
export type NewSession = InferInsertModel<typeof sessions>;

export type OAuthAccount = InferSelectModel<typeof oauth_accounts>;
export type NewOAuthAccount = InferInsertModel<typeof oauth_accounts>;

export type EmailVerificationToken = InferSelectModel<typeof email_verification_tokens>;
export type NewEmailVerificationToken = InferInsertModel<typeof email_verification_tokens>;

export type PasswordResetToken = InferSelectModel<typeof password_reset_tokens>;
export type NewPasswordResetToken = InferInsertModel<typeof password_reset_tokens>;

export type WaitlistEntry = InferSelectModel<typeof waitlist>;
export type NewWaitlistEntry = InferInsertModel<typeof waitlist>;

export type LaunchClaim = InferSelectModel<typeof launch_claims>;
export type NewLaunchClaim = InferInsertModel<typeof launch_claims>;

export type SecurityEvent = InferSelectModel<typeof security_events>;
export type NewSecurityEvent = InferInsertModel<typeof security_events>;
