import "./setup-env.js";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { eq } from "drizzle-orm";
import { createDatabase, closeDatabase, schema } from "@app/database";
import { processMemorySync } from "../src/processors/maintenance.js";
import { MemoryBudgetStore } from "@app/ai-contracts";
import type { WorkerContext } from "../src/context.js";
import type { AamosClient, AamosResult } from "@app/ai-contracts";

const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
const email = "memory-sync-test@example.invalid";

describe("UPDATE_USER_MEMORY hardening", () => {
  async function cleanup() {
    const existing = await testDb.db
      .select({ id: schema.users.id })
      .from(schema.users)
      .where(eq(schema.users.email, email));
    for (const u of existing) {
      await testDb.db.delete(schema.memoryItems).where(eq(schema.memoryItems.userId, u.id));
      await testDb.db.delete(schema.tasteSignals).where(eq(schema.tasteSignals.userId, u.id));
      await testDb.db.delete(schema.domainEvents).where(eq(schema.domainEvents.userId, u.id));
      await testDb.db.delete(schema.aiUsageCounters).where(eq(schema.aiUsageCounters.userId, u.id));
      await testDb.db.delete(schema.userConsents).where(eq(schema.userConsents.userId, u.id));
      await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id));
      await testDb.db.delete(schema.userCredentials).where(eq(schema.userCredentials.userId, u.id));
      await testDb.db.delete(schema.refreshTokens).where(eq(schema.refreshTokens.userId, u.id));
      await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id));
    }
  }

  beforeAll(async () => {
    await cleanup();
  });

  afterAll(async () => {
    await cleanup();
    await closeDatabase();
  });

  async function setupUserWithEvents(): Promise<{ userId: string; eventIds: string[] }> {
    await cleanup();
    const [user] = await testDb.db
      .insert(schema.users)
      .values({
        email,
        passwordHash: "not-used",
        displayName: "Memory Sync Test",
      })
      .returning();
    const userId = user!.id;

    await testDb.db.insert(schema.userConsents).values([
      { userId, kind: "personalization", status: "granted" },
      { userId, kind: "anonymized_improvement", status: "granted" },
      { userId, kind: "image_training", status: "granted" },
    ]);

    const events = await testDb.db
      .insert(schema.domainEvents)
      .values([
        {
          type: "RECIPE_COOKED",
          userId,
          payload: { recipeId: "r1", recipeTitleSv: "Kycklingpasta" },
        },
        {
          type: "RECIPE_RATED",
          userId,
          payload: { recipeId: "r1", stars: 5 },
        },
        {
          type: "MEAL_LOGGED",
          userId,
          payload: { mealType: "dinner" },
        },
      ])
      .returning();

    return { userId, eventIds: events.map((e) => e.id) };
  }

  function makeContext(aamos: AamosClient, budgetStore?: MemoryBudgetStore): WorkerContext {
    return {
      db: testDb.db,
      aamos,
      budgetStore,
      apiBaseUrl: "http://localhost:4000",
      storage: {} as any,
      readUrl: async (key: string) => `http://localhost:4000/mock-s3/${key}`,
      close: async () => {},
    };
  }

  it("skriver grundade förslag från events", async () => {
    const { userId, eventIds } = await setupUserWithEvents();

    const aamos = {
      async runTask() {
        return {
          status: "ok",
          output: {
            memoryUpdates: eventIds.map((id) => ({
              key: `likes_${id}`,
              kind: "structured_fact",
              summarySv: "Gillar kycklingpasta",
              value: { recipeId: "r1" },
              origin: "observed",
              confidence: 0.75,
              expiresAt: null,
              sourceEventIds: [id],
            })),
          },
          costUsd: 0.001,
          inputTokens: 100,
          outputTokens: 50,
        } as AamosResult<"UPDATE_USER_MEMORY">;
      },
      async healthCheck() {
        return { ok: true };
      },
    } as AamosClient;

    const updates = await processMemorySync(makeContext(aamos));
    expect(updates).toBe(eventIds.length);

    const items = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(items.length).toBe(eventIds.length);

    const usage = await testDb.db
      .select()
      .from(schema.aiUsageCounters)
      .where(eq(schema.aiUsageCounters.userId, userId));
    expect(usage).toHaveLength(1);
    expect(usage[0]!.aiCostUsdMicrocents).toBe(100_000); // 0.001 USD
    expect(usage[0]!.aiTokensIn).toBe(100);
    expect(usage[0]!.aiTokensOut).toBe(50);
  });

  it("avvisar fabricerade förslag utan event-stöd", async () => {
    const { userId, eventIds } = await setupUserWithEvents();

    const aamos = {
      async runTask() {
        return {
          status: "ok",
          output: {
            memoryUpdates: [
              {
                key: "fabricated_likes_sushi",
                kind: "structured_fact",
                summarySv: "Gillar sushi",
                value: { favoriteCuisine: "japanese" },
                origin: "ai_inferred",
                confidence: 0.5,
                expiresAt: null,
                sourceEventIds: ["non-existent-event-id"],
              },
              {
                key: "grounded_likes_pasta",
                kind: "structured_fact",
                summarySv: "Gillar pasta",
                value: { recipeId: "r1" },
                origin: "observed",
                confidence: 0.8,
                expiresAt: null,
                sourceEventIds: [eventIds[0]!],
              },
            ],
          },
          costUsd: 0,
          inputTokens: 0,
          outputTokens: 0,
        } as AamosResult<"UPDATE_USER_MEMORY">;
      },
      async healthCheck() {
        return { ok: true };
      },
    } as AamosClient;

    const updates = await processMemorySync(makeContext(aamos));
    expect(updates).toBe(1);

    const items = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(items.map((i) => i.key)).toEqual(["grounded_likes_pasta"]);
  });

  it("avvisar ai_inferred med för hög confidence", async () => {
    const { userId, eventIds } = await setupUserWithEvents();

    const aamos = {
      async runTask() {
        return {
          status: "ok",
          output: {
            memoryUpdates: [
              {
                key: "overconfident",
                kind: "structured_fact",
                summarySv: "Gillar pasta",
                value: { recipeId: "r1" },
                origin: "ai_inferred",
                confidence: 0.95,
                expiresAt: null,
                sourceEventIds: [eventIds[0]!],
              },
            ],
          },
          costUsd: 0,
          inputTokens: 0,
          outputTokens: 0,
        } as AamosResult<"UPDATE_USER_MEMORY">;
      },
      async healthCheck() {
        return { ok: true };
      },
    } as AamosClient;

    const updates = await processMemorySync(makeContext(aamos));
    expect(updates).toBe(0);

    const items = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(items).toHaveLength(0);
  });

  it("hoppas över anropet och skriver inget när daglig budget är förbrukad", async () => {
    const { userId } = await setupUserWithEvents();
    const previousBudget = process.env.GEMINI_DAILY_BUDGET_USD;
    process.env.GEMINI_DAILY_BUDGET_USD = "0.0005";

    const budgetStore = new MemoryBudgetStore();
    await budgetStore.incrementDailySpendUsd(0.0004); // redan nära taket

    let called = false;
    const aamos = {
      async runTask() {
        called = true;
        return {
          status: "ok",
          output: { memoryUpdates: [] },
          costUsd: 0,
          inputTokens: 0,
          outputTokens: 0,
        } as AamosResult<"UPDATE_USER_MEMORY">;
      },
      async healthCheck() {
        return { ok: true };
      },
    } as AamosClient;

    const updates = await processMemorySync(makeContext(aamos, budgetStore));
    expect(updates).toBe(0);
    expect(called).toBe(false);

    const items = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(items).toHaveLength(0);

    process.env.GEMINI_DAILY_BUDGET_USD = previousBudget;
  });

  it("förkastar trasigt AAMOS-svar helt utan delskrivning", async () => {
    const { userId } = await setupUserWithEvents();

    const aamos = {
      async runTask() {
        return {
          status: "ok",
          output: { memoryUpdates: [{ broken: true }] },
          costUsd: 0,
          inputTokens: 0,
          outputTokens: 0,
        } as unknown as AamosResult<"UPDATE_USER_MEMORY">;
      },
      async healthCheck() {
        return { ok: true };
      },
    } as AamosClient;

    const updates = await processMemorySync(makeContext(aamos));
    expect(updates).toBe(0);

    const items = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(items).toHaveLength(0);
  });
});
