import { and, eq, lte } from "drizzle-orm"
import { db } from "@/lib/db"
import {
  user as userTable,
  profiles,
  verification,
  consent_log,
  admin_audit_log,
  account_deletion_requests,
} from "@/lib/db/schema"
import { deleteUserStorage } from "@/lib/storage"
import { stripe } from "@/lib/stripe/client"
import { sendEmail, accountDeletionCompletedHtml } from "@/lib/email/send"

// ============================================================================
// GDPR account deletion (Article 17 — right to erasure).
//
// The DB schema does most of the cascading for us: every data table references
// profiles(id) ON DELETE CASCADE, and profiles references user(id) ON DELETE
// CASCADE — so deleting the auth user row erases the entire portfolio,
// sessions, and linked accounts in one statement. What the cascade CANNOT
// reach lives here: uploaded files in object storage, the Stripe
// subscription/customer, email-keyed verification rows, and PII embedded in
// the FK-less compliance tables (consent_log).
// ============================================================================

export type DeletionOutcome = {
  userId: string
  filesDeleted: number
  stripeSubscription: "cancelled" | "none" | "error"
  stripeCustomer: "deleted" | "none" | "error"
  /** PayPal has no server-side cancel integration — surfaced so ops can follow up. */
  paypalSubscriptionLeftActive: string | null
  userRowDeleted: boolean
}

/**
 * Irreversibly delete a user's account, data, files, and billing. Safe to call
 * for an already-deleted user (it still purges storage and returns cleanly).
 */
export async function executeAccountDeletion(userId: string): Promise<DeletionOutcome> {
  const outcome: DeletionOutcome = {
    userId,
    filesDeleted: 0,
    stripeSubscription: "none",
    stripeCustomer: "none",
    paypalSubscriptionLeftActive: null,
    userRowDeleted: false,
  }

  const profile = await db.query.profiles.findFirst({
    where: eq(profiles.id, userId),
    columns: {
      email: true,
      stripe_subscription_id: true,
      stripe_customer_id: true,
      paypal_subscription_id: true,
    },
  })

  // 1. Billing — stop money moving before the data disappears.
  if (profile?.stripe_subscription_id) {
    try {
      await stripe.subscriptions.cancel(profile.stripe_subscription_id)
      outcome.stripeSubscription = "cancelled"
    } catch (e) {
      // Already-cancelled subscriptions throw; treat "not found"-style errors as done.
      outcome.stripeSubscription = isStripeGone(e) ? "cancelled" : "error"
    }
  }
  if (profile?.stripe_customer_id) {
    try {
      await stripe.customers.del(profile.stripe_customer_id)
      outcome.stripeCustomer = "deleted"
    } catch (e) {
      outcome.stripeCustomer = isStripeGone(e) ? "deleted" : "error"
    }
  }
  if (profile?.paypal_subscription_id) {
    outcome.paypalSubscriptionLeftActive = profile.paypal_subscription_id
  }

  // 2. Stored files (Spaces / local disk) — outside the DB cascade.
  try {
    outcome.filesDeleted = await deleteUserStorage(userId)
  } catch (e) {
    console.error(`[gdpr] storage purge failed for ${userId}:`, e)
  }

  // 3. PII in FK-less compliance tables: keep the consent facts, drop identifiers.
  await db
    .update(consent_log)
    .set({ email: null, ip_address: null, user_agent: null })
    .where(eq(consent_log.user_id, userId))

  // 4. Email-keyed verification tokens (password reset / email verification).
  if (profile?.email) {
    await db.delete(verification).where(eq(verification.identifier, profile.email))
  }

  // 5. The user row — cascades profiles → every portfolio table, sessions, accounts.
  const deleted = await db.delete(userTable).where(eq(userTable.id, userId)).returning({ id: userTable.id })
  outcome.userRowDeleted = deleted.length > 0

  // Close out any open self-service request (covers admin-initiated deletes too).
  await db
    .update(account_deletion_requests)
    .set({ status: "completed", completed_at: new Date().toISOString(), email: null })
    .where(
      and(
        eq(account_deletion_requests.user_id, userId),
        eq(account_deletion_requests.status, "pending")
      )
    )

  // 6. Immutable evidence that the erasure ran (admin_id null = system action).
  await db.insert(admin_audit_log).values({
    admin_id: null,
    action: "gdpr_delete_account",
    target_user_id: userId,
    metadata: { ...outcome },
  })

  return outcome
}

function isStripeGone(e: unknown): boolean {
  const msg = e instanceof Error ? e.message : String(e)
  return /no such|already.*cancel|resource_missing/i.test(msg)
}

/**
 * Process deletion requests whose grace period has elapsed. Called by the
 * daily gdpr cron. Failures stay `pending` (with the error recorded) so the
 * next run retries them.
 */
export async function processDueDeletions(limit = 25): Promise<{ processed: number; deleted: number }> {
  const due = await db
    .select()
    .from(account_deletion_requests)
    .where(
      and(
        eq(account_deletion_requests.status, "pending"),
        lte(account_deletion_requests.scheduled_for, new Date().toISOString())
      )
    )
    .limit(limit)

  let deleted = 0
  for (const request of due) {
    const email = request.email
    try {
      const outcome = await executeAccountDeletion(request.user_id)
      await db
        .update(account_deletion_requests)
        .set({
          status: "completed",
          completed_at: new Date().toISOString(),
          // Data minimization: the request itself must not keep PII after erasure.
          email: null,
          metadata: { ...request.metadata, outcome },
        })
        .where(eq(account_deletion_requests.id, request.id))
      deleted++

      if (email) {
        await sendEmail({
          to: email,
          subject: "Your account and data have been deleted",
          html: accountDeletionCompletedHtml(),
        })
      }
    } catch (e) {
      console.error(`[gdpr] deletion failed for ${request.user_id}:`, e)
      await db
        .update(account_deletion_requests)
        .set({
          metadata: {
            ...request.metadata,
            last_error: e instanceof Error ? e.message : String(e),
            last_error_at: new Date().toISOString(),
          },
        })
        .where(eq(account_deletion_requests.id, request.id))
    }
  }

  return { processed: due.length, deleted }
}
