package main

import (
	"context"
	"flag"
	"fmt"
	"time"

	"apps/backend/internal/config"
	scoutRepo "apps/backend/internal/module/scout/repository"

	"github.com/zeromicro/go-zero/core/conf"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// init creates idempotent operational indexes only. It intentionally never
// inserts members or other business records.
func main() {
	configFile := flag.String("f", "etc/gateway.yaml", "config file")
	flag.Parse()

	var c config.Config
	conf.MustLoad(*configFile, &c)
	c.ApplyEnv()
	if c.Mongo.URI == "" || c.Mongo.Database == "" {
		panic("Mongo.URI and Mongo.Database are required")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()
	client, err := mongo.Connect(ctx, options.Client().ApplyURI(c.Mongo.URI))
	if err != nil {
		panic(err)
	}
	defer client.Disconnect(context.Background())

	db := client.Database(c.Mongo.Database)
	for collection, models := range indexModels() {
		if _, err := db.Collection(collection).Indexes().CreateMany(ctx, models); err != nil {
			panic(fmt.Errorf("create %s indexes: %w", collection, err))
		}
	}
	fmt.Println("database indexes initialized")
}

// ownerIndex covers the near-universal "list one member's rows, newest first" shape. The sort key
// belongs in the index too, otherwise Mongo fetches every matching row before sorting.
func ownerIndex(sortField, name string) mongo.IndexModel {
	return mongo.IndexModel{
		Keys:    bson.D{{Key: "owner_uid", Value: 1}, {Key: sortField, Value: -1}},
		Options: options.Index().SetName(name),
	}
}

// indexModels lists every index the running code depends on. Owner-scoped collections were all
// unindexed, so each list endpoint was a full collection scan that grew with total rows across
// every member rather than with the member's own data.
//
// These are deliberately all non-unique. Adding a uniqueness constraint to existing data can make
// CreateMany fail and take a deploy down, so that belongs in its own migration with a duplicate
// pre-check rather than here.
//
// Never list an index here that a migration creates as unique: Mongo rejects a second createIndex
// with the same name and a different spec (IndexKeySpecsConflict, code 86), so the duplicate would
// make this whole command panic once the migration has run.
func indexModels() map[string][]mongo.IndexModel {
	return map[string][]mongo.IndexModel{
		// members.uid is read on every authenticated request via the auth middleware, and it is a
		// field lookup rather than _id, so without this it scanned the whole collection whenever
		// the Redis cache missed.
		"members": {
			{Keys: bson.D{{Key: "uid", Value: 1}}, Options: options.Index().SetName("member_uid")},
			{Keys: bson.D{{Key: "email", Value: 1}}, Options: options.Index().SetName("member_email")},
			{Keys: bson.D{{Key: "phone", Value: 1}}, Options: options.Index().SetName("member_phone")},
		},
		"identities": {
			{Keys: bson.D{{Key: "login_id", Value: 1}, {Key: "platform", Value: 1}}, Options: options.Index().SetName("identity_login_platform")},
		},
		"member_settings": {
			{Keys: bson.D{{Key: "uid", Value: 1}}, Options: options.Index().SetName("settings_uid")},
		},
		"jobs": {
			{Keys: bson.D{{Key: "status", Value: 1}, {Key: "run_after", Value: 1}, {Key: "created_at", Value: 1}}, Options: options.Index().SetName("claim_due_jobs")},
			{Keys: bson.D{{Key: "status", Value: 1}, {Key: "completed_at", Value: 1}}, Options: options.Index().SetName("purge_terminal_jobs")},
			// The jobs list is polled continuously by the UI and runs a count plus a find per poll.
			ownerIndex("updated_at", "owner_jobs_updated"),
		},
		"scout_runs":            scoutRepo.RunIndexModels(),
		"scout_seen_identities": scoutRepo.SeenIdentityIndexModels(),
		// One notification is written per job state change and nothing purges them, so this
		// collection grows without bound and its scan cost grows with it.
		"notifications": {
			ownerIndex("created_at", "owner_notifications_created"),
		},
		"threads_accounts": {
			ownerIndex("created_at", "owner_accounts_created"),
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "threads_user_id", Value: 1}}, Options: options.Index().SetName("owner_threads_user")},
		},
		"studio_outbox": {
			{Keys: bson.D{{Key: "status", Value: 1}, {Key: "updated_at", Value: 1}}, Options: options.Index().SetName("worker_outbox_status_updated")},
			{Keys: bson.D{{Key: "steps.status", Value: 1}, {Key: "steps.scheduled_at", Value: 1}, {Key: "steps.lease_expires_at", Value: 1}}, Options: options.Index().SetName("claim_due_outbox_steps")},
			ownerIndex("updated_at", "owner_outbox_updated"),
		},
		"studio_personas": {ownerIndex("updated_at", "owner_personas_updated")},
		"studio_plays":    {ownerIndex("updated_at", "owner_plays_updated")},
		"studio_own_posts": {
			ownerIndex("published_at", "owner_own_posts_published"),
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "account_id", Value: 1}, {Key: "published_at", Value: -1}}, Options: options.Index().SetName("owner_account_own_posts")},
		},
		"studio_mentions": {
			ownerIndex("created_at", "owner_mentions_created"),
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "account_id", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("owner_account_mentions")},
		},
		"scout_brands":   {{Keys: bson.D{{Key: "owner_uid", Value: 1}}, Options: options.Index().SetName("owner_brands")}},
		"scout_products": {{Keys: bson.D{{Key: "owner_uid", Value: 1}}, Options: options.Index().SetName("owner_products")}},
		"scout_posts": append([]mongo.IndexModel{
			ownerIndex("created_at", "owner_posts_created"),
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "brand_id", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("owner_brand_posts")},
		}, scoutRepo.RunPostIndexModels()...),
		"scout_homework":   {{Keys: bson.D{{Key: "owner_uid", Value: 1}}, Options: options.Index().SetName("owner_homework")}},
		"inspire_elements": {ownerIndex("updated_at", "owner_elements_updated")},
		"inspire_sessions": {ownerIndex("updated_at", "owner_sessions_updated")},
		"usage_events": {
			{Keys: bson.D{{Key: "uid", Value: 1}, {Key: "month_key", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("uid_month_events")},
			{Keys: bson.D{{Key: "month_key", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("tenant_month_events")},
			{Keys: bson.D{{Key: "created_at", Value: 1}}, Options: options.Index().SetName("events_created_range")},
		},
		"usage_prefs": {
			{Keys: bson.D{{Key: "uid", Value: 1}}, Options: options.Index().SetName("prefs_uid")},
			// Stripe webhooks arrive knowing only the customer, so this is the reverse lookup
			// back to a member and it runs on every billing event.
			{Keys: bson.D{{Key: "stripe_customer_id", Value: 1}}, Options: options.Index().SetName("prefs_stripe_customer")},
		},
		"usage_purchases": {
			{Keys: bson.D{{Key: "uid", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("uid_purchases_created")},
			{Keys: bson.D{{Key: "stripe_event_id", Value: 1}}, Options: options.Index().SetName("purchase_stripe_event")},
		},
		"growth_outcomes": {
			// The observe worker scans by status across all tenants on every tick.
			{Keys: bson.D{{Key: "status", Value: 1}}, Options: options.Index().SetName("observe_status")},
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "source_type", Value: 1}, {Key: "source_id", Value: 1}}, Options: options.Index().SetName("owner_outcome_source")},
			ownerIndex("created_at", "owner_outcomes_created"),
		},
		"growth_checkups":       {ownerIndex("created_at", "owner_checkups_created")},
		"growth_account_health": {{Keys: bson.D{{Key: "owner_uid", Value: 1}}, Options: options.Index().SetName("owner_health")}},
		"growth_workspaces":     {{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "archived", Value: 1}}, Options: options.Index().SetName("owner_workspaces_archived")}},
		"growth_draft_reviews": {
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "ref_type", Value: 1}, {Key: "ref_id", Value: 1}, {Key: "updated_at", Value: -1}}, Options: options.Index().SetName("owner_review_ref")},
		},
		"growth_invite_rewards": {
			{Keys: bson.D{{Key: "inviter_uid", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("inviter_rewards_created")},
			{Keys: bson.D{{Key: "invitee_uid", Value: 1}}, Options: options.Index().SetName("invitee_reward")},
		},
		"growth_utm_links": {
			// Looked up by code on the unauthenticated public redirect.
			{Keys: bson.D{{Key: "code", Value: 1}}, Options: options.Index().SetName("utm_code")},
			ownerIndex("created_at", "owner_utm_created"),
		},
		"growth_ws_members": {
			{Keys: bson.D{{Key: "workspace_id", Value: 1}, {Key: "uid", Value: 1}}, Options: options.Index().SetName("workspace_member")},
		},
		// demand-radar. Names and specs match migration 000014 exactly so both paths are
		// idempotent; the two unique keys (radar_opportunities.owner_opportunity_external and
		// crm_contacts.owner_contact_identity) are intentionally absent and owned by that migration.
		"radar_watches": {
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "status", Value: 1}}, Options: options.Index().SetName("owner_watches_status")},
			ownerIndex("created_at", "owner_watches_created"),
		},
		"radar_sweeps": {
			ownerIndex("created_at", "owner_sweeps_created"),
			{Keys: bson.D{{Key: "watch_id", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("watch_sweeps_created")},
			{Keys: bson.D{{Key: "job_id", Value: 1}}, Options: options.Index().SetName("sweep_job")},
		},
		"radar_opportunities": {
			ownerIndex("created_at", "owner_opportunities_created"),
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "intent_band", Value: 1}, {Key: "status", Value: 1}}, Options: options.Index().SetName("owner_opportunity_band_status")},
		},
		"radar_replies": {
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "opportunity_id", Value: 1}, {Key: "variant", Value: 1}}, Options: options.Index().SetName("owner_reply_variant")},
		},
		"crm_contacts": {
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "stage", Value: 1}, {Key: "last_touch_at", Value: -1}}, Options: options.Index().SetName("owner_contacts_stage_touch")},
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "needs_follow_up", Value: 1}}, Options: options.Index().SetName("owner_contacts_follow_up")},
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "removed_at", Value: 1}, {Key: "stage", Value: 1}, {Key: "last_touch_at", Value: -1}}, Options: options.Index().SetName("owner_contacts_active_stage_touch")},
		},
		"crm_touches": {
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "contact_id", Value: 1}, {Key: "created_at", Value: -1}}, Options: options.Index().SetName("owner_touches_created")},
		},
		// The follow-up scan is a cross-tenant due sweep, so its driving index is not owner-scoped.
		"crm_followups": {
			{Keys: bson.D{{Key: "status", Value: 1}, {Key: "due_at", Value: 1}}, Options: options.Index().SetName("followup_due_scan")},
			{Keys: bson.D{{Key: "owner_uid", Value: 1}, {Key: "contact_id", Value: 1}}, Options: options.Index().SetName("owner_contact_followup")},
		},
		"billing_checkout_attempts": {
			{Keys: bson.D{{Key: "uid", Value: 1}, {Key: "request_id", Value: 1}}, Options: options.Index().SetName("uid_request")},
			{Keys: bson.D{{Key: "session_id", Value: 1}}, Options: options.Index().SetName("checkout_session")},
			{Keys: bson.D{{Key: "customer_id", Value: 1}}, Options: options.Index().SetName("checkout_customer")},
		},
	}
}
