/**
 * UserCredential Model Tests
 *
 * Tests the UserCredential Prisma model including:
 * - Model structure and constraints
 * - Enum validation
 * - Unique constraints
 * - Foreign key relationships
 * - Default values
 *
 * Note: RLS policy tests are in user-credential-rls.spec.ts
 * Note: Encryption tests are in user-credential-encryption.middleware.spec.ts
 */

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { PrismaClient, CredentialType, CredentialScope } from "@prisma/client";

const shouldRunDbIntegrationTests =
  process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);

const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;

describeFn("UserCredential Model", () => {
  let prisma: PrismaClient;
  let testUserId: string;
  let testWorkspaceId: string;

  beforeAll(async () => {
    // Note: These tests require a running database
    // They will be skipped in CI if DATABASE_URL is not set
    if (!shouldRunDbIntegrationTests) {
      console.warn("Skipping UserCredential model tests (set RUN_DB_TESTS=true and DATABASE_URL)");
      return;
    }

    prisma = new PrismaClient();

    // Create test user and workspace
    const user = await prisma.user.create({
      data: {
        email: `test-${Date.now()}@example.com`,
        name: "Test User",
        emailVerified: true,
      },
    });
    testUserId = user.id;

    const workspace = await prisma.workspace.create({
      data: {
        name: `Test Workspace ${Date.now()}`,
        ownerId: testUserId,
      },
    });
    testWorkspaceId = workspace.id;
  });

  afterAll(async () => {
    if (!prisma) return;

    // Clean up test data
    await prisma.userCredential.deleteMany({
      where: { userId: testUserId },
    });
    await prisma.workspace.deleteMany({
      where: { id: testWorkspaceId },
    });
    await prisma.user.deleteMany({
      where: { id: testUserId },
    });

    await prisma.$disconnect();
  });

  describe("Model Structure", () => {
    it("should create a user-scoped credential with all required fields", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Test API Key",
          provider: "github",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "test:encrypted:value",
          maskedValue: "****1234",
        },
      });

      expect(credential.id).toBeDefined();
      expect(credential.userId).toBe(testUserId);
      expect(credential.workspaceId).toBeNull();
      expect(credential.name).toBe("Test API Key");
      expect(credential.provider).toBe("github");
      expect(credential.type).toBe(CredentialType.API_KEY);
      expect(credential.scope).toBe(CredentialScope.USER);
      expect(credential.encryptedValue).toBe("test:encrypted:value");
      expect(credential.maskedValue).toBe("****1234");
      expect(credential.isActive).toBe(true); // Default value
      expect(credential.metadata).toEqual({}); // Default value
      expect(credential.createdAt).toBeInstanceOf(Date);
      expect(credential.updatedAt).toBeInstanceOf(Date);
    });

    it("should create a workspace-scoped credential", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          workspaceId: testWorkspaceId,
          name: "Workspace Token",
          provider: "openai",
          type: CredentialType.ACCESS_TOKEN,
          scope: CredentialScope.WORKSPACE,
          encryptedValue: "workspace:encrypted:value",
        },
      });

      expect(credential.scope).toBe(CredentialScope.WORKSPACE);
      expect(credential.workspaceId).toBe(testWorkspaceId);
    });

    it("should support all CredentialType enum values", async () => {
      if (!prisma) return;

      const types = [
        CredentialType.API_KEY,
        CredentialType.OAUTH_TOKEN,
        CredentialType.ACCESS_TOKEN,
        CredentialType.SECRET,
        CredentialType.PASSWORD,
        CredentialType.CUSTOM,
      ];

      for (const type of types) {
        const credential = await prisma.userCredential.create({
          data: {
            userId: testUserId,
            name: `Test ${type}`,
            provider: "test",
            type,
            scope: CredentialScope.USER,
            encryptedValue: `encrypted:${type}:value`,
          },
        });

        expect(credential.type).toBe(type);
      }
    });

    it("should support all CredentialScope enum values", async () => {
      if (!prisma) return;

      const scopes = [CredentialScope.USER, CredentialScope.WORKSPACE, CredentialScope.SYSTEM];

      for (const scope of scopes) {
        const credential = await prisma.userCredential.create({
          data: {
            userId: testUserId,
            workspaceId: scope === CredentialScope.WORKSPACE ? testWorkspaceId : null,
            name: `Test ${scope}`,
            provider: "test",
            type: CredentialType.API_KEY,
            scope,
            encryptedValue: `encrypted:${scope}:value`,
          },
        });

        expect(credential.scope).toBe(scope);
      }
    });
  });

  describe("Optional Fields", () => {
    it("should allow optional fields to be null or undefined", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Minimal Credential",
          provider: "custom",
          type: CredentialType.CUSTOM,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:minimal:value",
          // All optional fields omitted
        },
      });

      expect(credential.workspaceId).toBeNull();
      expect(credential.maskedValue).toBeNull();
      expect(credential.description).toBeNull();
      expect(credential.expiresAt).toBeNull();
      expect(credential.lastUsedAt).toBeNull();
      expect(credential.rotatedAt).toBeNull();
    });

    it("should allow all optional fields to be set", async () => {
      if (!prisma) return;

      const now = new Date();
      const expiresAt = new Date(Date.now() + 86400000); // 24 hours

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          workspaceId: testWorkspaceId,
          name: "Full Credential",
          provider: "github",
          type: CredentialType.OAUTH_TOKEN,
          scope: CredentialScope.WORKSPACE,
          encryptedValue: "encrypted:full:value",
          maskedValue: "****xyz",
          description: "This is a test credential",
          expiresAt,
          lastUsedAt: now,
          rotatedAt: now,
          metadata: { customField: "customValue" },
          isActive: false,
        },
      });

      expect(credential.workspaceId).toBe(testWorkspaceId);
      expect(credential.maskedValue).toBe("****xyz");
      expect(credential.description).toBe("This is a test credential");
      expect(credential.expiresAt).toEqual(expiresAt);
      expect(credential.lastUsedAt).toEqual(now);
      expect(credential.rotatedAt).toEqual(now);
      expect(credential.metadata).toEqual({ customField: "customValue" });
      expect(credential.isActive).toBe(false);
    });
  });

  describe("Unique Constraints", () => {
    it("should enforce unique constraint on (userId, workspaceId, provider, name)", async () => {
      if (!prisma) return;

      // Create first credential
      await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Duplicate Test",
          provider: "github",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:value:1",
        },
      });

      // Attempt to create duplicate
      await expect(
        prisma.userCredential.create({
          data: {
            userId: testUserId,
            name: "Duplicate Test",
            provider: "github",
            type: CredentialType.API_KEY,
            scope: CredentialScope.USER,
            encryptedValue: "encrypted:value:2",
          },
        })
      ).rejects.toThrow(/Unique constraint/);
    });

    it("should allow same name for different providers", async () => {
      if (!prisma) return;

      const name = "API Key";

      const github = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name,
          provider: "github",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:github:value",
        },
      });

      const openai = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name,
          provider: "openai",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:openai:value",
        },
      });

      expect(github.provider).toBe("github");
      expect(openai.provider).toBe("openai");
    });

    it("should allow same name for different workspaces", async () => {
      if (!prisma) return;

      const name = "Workspace Token";

      const workspace1 = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          workspaceId: testWorkspaceId,
          name,
          provider: "test",
          type: CredentialType.ACCESS_TOKEN,
          scope: CredentialScope.WORKSPACE,
          encryptedValue: "encrypted:ws1:value",
        },
      });

      // Create second workspace for test
      const workspace2 = await prisma.workspace.create({
        data: {
          name: `Test Workspace 2 ${Date.now()}`,
          ownerId: testUserId,
        },
      });

      const credential2 = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          workspaceId: workspace2.id,
          name,
          provider: "test",
          type: CredentialType.ACCESS_TOKEN,
          scope: CredentialScope.WORKSPACE,
          encryptedValue: "encrypted:ws2:value",
        },
      });

      expect(workspace1.workspaceId).toBe(testWorkspaceId);
      expect(credential2.workspaceId).toBe(workspace2.id);

      // Cleanup
      await prisma.workspace.delete({ where: { id: workspace2.id } });
    });
  });

  describe("Foreign Key Relations", () => {
    it("should cascade delete when user is deleted", async () => {
      if (!prisma) return;

      // Create temporary user
      const tempUser = await prisma.user.create({
        data: {
          email: `temp-${Date.now()}@example.com`,
          name: "Temp User",
        },
      });

      // Create credential for temp user
      const credential = await prisma.userCredential.create({
        data: {
          userId: tempUser.id,
          name: "Temp Credential",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:temp:value",
        },
      });

      // Delete user
      await prisma.user.delete({ where: { id: tempUser.id } });

      // Credential should be deleted
      const deletedCredential = await prisma.userCredential.findUnique({
        where: { id: credential.id },
      });
      expect(deletedCredential).toBeNull();
    });

    it("should cascade delete when workspace is deleted", async () => {
      if (!prisma) return;

      // Create temporary workspace
      const tempWorkspace = await prisma.workspace.create({
        data: {
          name: `Temp Workspace ${Date.now()}`,
          ownerId: testUserId,
        },
      });

      // Create workspace-scoped credential
      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          workspaceId: tempWorkspace.id,
          name: "Workspace Credential",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.WORKSPACE,
          encryptedValue: "encrypted:workspace:value",
        },
      });

      // Delete workspace
      await prisma.workspace.delete({ where: { id: tempWorkspace.id } });

      // Credential should be deleted
      const deletedCredential = await prisma.userCredential.findUnique({
        where: { id: credential.id },
      });
      expect(deletedCredential).toBeNull();
    });

    it("should include user relation", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Relation Test",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:relation:value",
        },
        include: {
          user: true,
        },
      });

      expect(credential.user).toBeDefined();
      expect(credential.user.id).toBe(testUserId);
    });

    it("should include workspace relation when workspace-scoped", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          workspaceId: testWorkspaceId,
          name: "Workspace Relation Test",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.WORKSPACE,
          encryptedValue: "encrypted:workspace:relation:value",
        },
        include: {
          workspace: true,
        },
      });

      expect(credential.workspace).toBeDefined();
      expect(credential.workspace?.id).toBe(testWorkspaceId);
    });
  });

  describe("Timestamps", () => {
    it("should auto-set createdAt and updatedAt on create", async () => {
      if (!prisma) return;

      const before = new Date();
      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Timestamp Test",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:timestamp:value",
        },
      });
      const after = new Date();

      expect(credential.createdAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
      expect(credential.createdAt.getTime()).toBeLessThanOrEqual(after.getTime());
      expect(credential.updatedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
      expect(credential.updatedAt.getTime()).toBeLessThanOrEqual(after.getTime());
    });

    it("should auto-update updatedAt on update", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Update Test",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:update:value",
        },
      });

      const originalUpdatedAt = credential.updatedAt;

      // Wait a bit to ensure timestamp difference
      await new Promise((resolve) => setTimeout(resolve, 10));

      const updated = await prisma.userCredential.update({
        where: { id: credential.id },
        data: { description: "Updated description" },
      });

      expect(updated.updatedAt.getTime()).toBeGreaterThan(originalUpdatedAt.getTime());
      expect(updated.createdAt).toEqual(credential.createdAt); // createdAt unchanged
    });
  });

  describe("Metadata JSONB", () => {
    it("should store and retrieve JSON metadata", async () => {
      if (!prisma) return;

      const metadata = {
        scopes: ["repo", "user"],
        tokenType: "bearer",
        expiresIn: 3600,
        customField: { nested: "value" },
      };

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Metadata Test",
          provider: "github",
          type: CredentialType.OAUTH_TOKEN,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:metadata:value",
          metadata,
        },
      });

      expect(credential.metadata).toEqual(metadata);
    });

    it("should allow empty metadata object", async () => {
      if (!prisma) return;

      const credential = await prisma.userCredential.create({
        data: {
          userId: testUserId,
          name: "Empty Metadata",
          provider: "test",
          type: CredentialType.API_KEY,
          scope: CredentialScope.USER,
          encryptedValue: "encrypted:empty:value",
        },
      });

      expect(credential.metadata).toEqual({});
    });
  });
});
