import { Alepha, z } from "alepha";
import { describe, expect, it } from "vitest";
import {
  $entity,
  $repository,
  DatabaseProvider,
  db,
  sql,
} from "../core/index.ts";
import {
  NodeSqliteProvider,
  nodeSqliteOptions,
} from "../core/providers/drivers/NodeSqliteProvider.ts";
import { AlephaOrmPostgres } from "../postgres/index.ts";

/**
 * SQL Injection Security Tests
 *
 * This test suite validates that the ORM properly sanitizes and escapes user input
 * to prevent SQL injection attacks across various scenarios:
 * - Basic filter operators (eq, ne, like, ilike, etc.)
 * - JSONB queries
 * - Array operators
 * - Raw SQL queries
 * - Both PostgreSQL and SQLite
 */
describe("SQL Injection Security Tests", () => {
  // Define test entities with various column types
  const users = $entity({
    name: "users",
    schema: z.object({
      id: db.primaryKey(),
      username: z.text(),
      email: z.text(),
      age: z.integer(),
      profile: z.object({
        bio: z.text(),
        settings: z.object({
          theme: z.text(),
          notifications: z.boolean(),
        }),
      }),
      tags: z.array(z.text()),
      metadata: z.object({
        permissions: z.array(z.text()),
        lastLogin: z.text().optional(),
      }),
    }),
  });

  class App {
    users = $repository(users);
  }

  const sqlInjectionPayloads = [
    // Classic SQL injection patterns
    "' OR '1'='1",
    "' OR 1=1--",
    "' OR 1=1#",
    "' OR 1=1/*",
    "admin'--",
    "admin' #",
    "admin'/*",

    // Union-based injection
    "' UNION SELECT NULL--",
    "' UNION SELECT NULL, NULL--",
    "' UNION ALL SELECT NULL--",

    // Stacked queries
    "'; DROP TABLE users--",
    "'; DELETE FROM users--",
    "'; UPDATE users SET username='hacked'--",

    // Boolean-based blind injection
    "' AND 1=1--",
    "' AND 1=2--",
    "' AND SUBSTRING(@@version,1,1)='5",

    // Time-based blind injection
    "'; WAITFOR DELAY '00:00:05'--",
    "'; SELECT SLEEP(5)--",
    "' AND (SELECT * FROM (SELECT(SLEEP(5)))a)--",

    // Quote escaping attempts
    "''",
    "\\'",
    "\\x27",
    "%27",

    // Comment variations
    "--",
    "-- -",
    "#",
    "/**/",
    "/* comment */",

    // Hex encoding
    "0x27",
    "0x4F0x52",

    // Special characters
    "';!--\"<XSS>=&{()}",
    "@@version",
    "char(39)",

    // PostgreSQL specific
    "$1",
    "$$",
    "';--",
    "1; SELECT version();",

    // SQLite specific
    "' || '1",
    "'; ATTACH DATABASE 'file' AS db;--",

    // JSONB injection attempts (PostgreSQL)
    "' -> 'key",
    "' ->> 'key",
    "' #> '{key}'",
    "' #>> '{key}'",
    '\' @> \'{"key":"value"}\'',
    "' ? 'key",
    "' ?| array['key']",
    "' ?& array['key']",
  ];

  describe("PostgreSQL", () => {
    describe("Basic Filter Operators", () => {
      it("should prevent SQL injection in eq operator", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        // Create a legitimate user
        await app.users.create({
          username: "alice",
          email: "alice@example.com",
          age: 30,
          profile: {
            bio: "Hello world",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["admin"],
          metadata: { permissions: ["read", "write"], lastLogin: "2024-01-01" },
        });

        // Try each SQL injection payload
        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.findMany({
            where: { username: { eq: payload } },
          });

          // Should return empty array since payload doesn't match any real username
          expect(result).toEqual([]);
        }
      });

      it("should prevent SQL injection in ne operator", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "bob",
          email: "bob@example.com",
          age: 25,
          profile: {
            bio: "Developer",
            settings: { theme: "light", notifications: false },
          },
          tags: [],
          metadata: { permissions: ["read"] },
        });

        for (const payload of sqlInjectionPayloads) {
          // Should not cause SQL errors or unexpected behavior
          const result = await app.users.findMany({
            where: { username: { ne: payload } },
          });

          // Should return the legitimate user
          expect(result.length).toBeGreaterThanOrEqual(0);
        }
      });

      it("should prevent SQL injection in contains operator", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "charlie",
          email: "charlie@example.com",
          age: 35,
          profile: {
            bio: "Designer",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["designer"],
          metadata: { permissions: ["read", "write"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.findMany({
            where: { username: { contains: payload } },
          });

          // Should safely handle the payload as a literal string
          expect(result).toEqual([]);
        }
      });

      it("should prevent SQL injection in contains operator (email field)", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "Diana",
          email: "diana@example.com",
          age: 28,
          profile: {
            bio: "Manager",
            settings: { theme: "light", notifications: true },
          },
          tags: ["manager"],
          metadata: { permissions: ["admin"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.findMany({
            where: { email: { contains: payload } },
          });

          expect(result).toEqual([]);
        }
      });

      it("should prevent SQL injection in gt/gte/lt/lte operators", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "eve",
          email: "eve@example.com",
          age: 40,
          profile: {
            bio: "Executive",
            settings: { theme: "dark", notifications: false },
          },
          tags: [],
          metadata: { permissions: ["admin", "read", "write"] },
        });

        // Numeric operators should either reject non-numeric payloads or treat them safely
        // PostgreSQL will throw a type error for invalid integer syntax
        // This is actually GOOD - the database layer is rejecting malicious input
        for (const payload of sqlInjectionPayloads) {
          try {
            const result = await app.users.findMany({
              where: { age: { gt: payload as any } },
            });

            // If it doesn't throw, it should return safe results
            expect(Array.isArray(result)).toBe(true);
          } catch (error) {
            // PostgreSQL rejects invalid integer syntax - this is expected and safe
            // The SQL is still parameterized, preventing injection
            expect(error).toBeDefined();
          }
        }
      });

      it("should prevent SQL injection in inArray operator", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "frank",
          email: "frank@example.com",
          age: 33,
          profile: {
            bio: "Developer",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["dev"],
          metadata: { permissions: ["read"] },
        });

        const result = await app.users.findMany({
          where: {
            username: { inArray: sqlInjectionPayloads.slice(0, 10) },
          },
        });

        // Should treat each payload as a literal string to compare
        expect(result).toEqual([]);
      });
    });

    describe("Array Operator Injection", () => {
      it("should prevent SQL injection in arrayContains", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "jack",
          email: "jack@example.com",
          age: 36,
          profile: {
            bio: "Security Expert",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["security", "expert"],
          metadata: { permissions: ["admin", "audit"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.findMany({
            where: {
              tags: { arrayContains: [payload] },
            },
          });

          expect(result).toEqual([]);
        }
      });

      it("should prevent SQL injection in arrayOverlaps", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "kate",
          email: "kate@example.com",
          age: 32,
          profile: {
            bio: "Product Manager",
            settings: { theme: "light", notifications: false },
          },
          tags: ["product", "manager"],
          metadata: { permissions: ["read", "write"] },
        });

        const result = await app.users.findMany({
          where: {
            tags: { arrayOverlaps: sqlInjectionPayloads.slice(0, 5) },
          },
        });

        expect(result).toEqual([]);
      });

      it("should prevent SQL injection in arrayContained", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "leo",
          email: "leo@example.com",
          age: 38,
          profile: {
            bio: "CTO",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["cto", "leadership"],
          metadata: { permissions: ["admin", "owner"] },
        });

        const result = await app.users.findMany({
          where: {
            tags: { arrayContained: sqlInjectionPayloads.slice(0, 5) },
          },
        });

        expect(result).toEqual([]);
      });
    });

    describe("Raw SQL Injection", () => {
      it("should safely handle parameterized queries", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "maria",
          email: "maria@example.com",
          age: 26,
          profile: {
            bio: "Designer",
            settings: { theme: "light", notifications: true },
          },
          tags: ["design"],
          metadata: { permissions: ["read"] },
        });

        for (const payload of sqlInjectionPayloads) {
          // Using parameterized queries should be safe
          const result = await app.users.query(
            (t) => sql`SELECT * FROM ${t} WHERE ${t.username} = ${payload}`,
            users.schema.pick({ username: true }),
          );

          // Should treat payload as a literal value
          expect(result).toEqual([]);
        }
      });

      it("should prevent injection in complex SQL expressions", async () => {
        const alepha = Alepha.create().with(AlephaOrmPostgres);
        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "nathan",
          email: "nathan@example.com",
          age: 41,
          profile: {
            bio: "Consultant",
            settings: { theme: "dark", notifications: false },
          },
          tags: ["consulting"],
          metadata: { permissions: ["read", "write", "admin"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.query(
            (t) =>
              sql`SELECT * FROM ${t} WHERE ${t.username} LIKE ${`%${payload}%`}`,
            users.schema.pick({ username: true }),
          );

          expect(result).toEqual([]);
        }
      });
    });
  });

  describe("SQLite", () => {
    describe("Basic Filter Operators", () => {
      it("should prevent SQL injection in eq operator", async () => {
        const alepha = Alepha.create().with({
          provide: DatabaseProvider,
          use: NodeSqliteProvider,
        });

        alepha.store.mut(nodeSqliteOptions, (old) => ({
          ...old,
          path: "sqlite://:memory:",
        }));

        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "sqlite_alice",
          email: "alice@sqlite.com",
          age: 30,
          profile: {
            bio: "SQLite tester",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["sqlite"],
          metadata: { permissions: ["read"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.findMany({
            where: { username: { eq: payload } },
          });

          expect(result).toEqual([]);
        }
      });

      it("should prevent SQL injection in contains operator (SQLite)", async () => {
        const alepha = Alepha.create().with({
          provide: DatabaseProvider,
          use: NodeSqliteProvider,
        });

        alepha.store.mut(nodeSqliteOptions, (old) => ({
          ...old,
          path: "sqlite://:memory:",
        }));

        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "sqlite_bob",
          email: "bob@sqlite.com",
          age: 25,
          profile: {
            bio: "SQLite developer",
            settings: { theme: "light", notifications: false },
          },
          tags: ["dev"],
          metadata: { permissions: ["read", "write"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.findMany({
            where: { username: { contains: payload } },
          });

          expect(result).toEqual([]);
        }
      });

      it("should prevent SQL injection in inArray operator", async () => {
        const alepha = Alepha.create().with({
          provide: DatabaseProvider,
          use: NodeSqliteProvider,
        });

        alepha.store.mut(nodeSqliteOptions, (old) => ({
          ...old,
          path: "sqlite://:memory:",
        }));

        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "sqlite_charlie",
          email: "charlie@sqlite.com",
          age: 35,
          profile: {
            bio: "SQLite admin",
            settings: { theme: "dark", notifications: true },
          },
          tags: ["admin"],
          metadata: { permissions: ["admin"] },
        });

        const result = await app.users.findMany({
          where: {
            username: { inArray: sqlInjectionPayloads.slice(0, 10) },
          },
        });

        expect(result).toEqual([]);
      });
    });

    describe("Raw SQL Injection (SQLite)", () => {
      it("should safely handle parameterized queries", async () => {
        const alepha = Alepha.create().with({
          provide: DatabaseProvider,
          use: NodeSqliteProvider,
        });

        alepha.store.mut(nodeSqliteOptions, (old) => ({
          ...old,
          path: "sqlite://:memory:",
        }));

        const app = alepha.inject(App);
        await alepha.start();

        await app.users.create({
          username: "sqlite_eve",
          email: "eve@sqlite.com",
          age: 40,
          profile: {
            bio: "SQL expert",
            settings: { theme: "light", notifications: false },
          },
          tags: ["sql"],
          metadata: { permissions: ["admin"] },
        });

        for (const payload of sqlInjectionPayloads) {
          const result = await app.users.query(
            (t) => sql`SELECT * FROM ${t} WHERE ${t.username} = ${payload}`,
            users.schema.pick({ username: true }),
          );

          expect(result).toEqual([]);
        }
      });
    });
  });

  describe("Edge Cases and Special Characters", () => {
    it("should handle unicode and special characters safely", async () => {
      const alepha = Alepha.create().with(AlephaOrmPostgres);
      const app = alepha.inject(App);
      await alepha.start();

      const specialChars = [
        "用户名", // Chinese
        "пользователь", // Russian
        "مستخدم", // Arabic
        "🔥💻🚀", // Emojis
        // Note: \u0000 (null byte) is not supported by PostgreSQL (invalid encoding)
        "\n\r\t", // Whitespace
        "\\\\", // Backslashes
        "<<<>>>", // Angle brackets
      ];

      for (const char of specialChars) {
        try {
          const created = await app.users.create({
            username: char,
            email: `${char}@example.com`,
            age: 25,
            profile: {
              bio: char,
              settings: { theme: "dark", notifications: true },
            },
            tags: [char],
            metadata: { permissions: [char] },
          });

          const found = await app.users.getOne({
            where: { username: { eq: char } },
          });

          expect(found.id).toBe(created.id);
          expect(found.username).toBe(char);
        } catch (error) {
          // Some special characters may have database-specific limitations
          // (e.g., PostgreSQL doesn't support null bytes in text fields)
          // The important thing is that it fails safely without SQL injection
          expect(error).toBeDefined();
        }
      }
    });

    it("should handle long strings with injection attempts safely", async () => {
      const alepha = Alepha.create().with(AlephaOrmPostgres);
      const app = alepha.inject(App);
      await alepha.start();

      // Create a string that's close to max length and contains SQL injection attempt
      const longString = `${"A".repeat(100)}' OR '1'='1${"B".repeat(100)}`;

      const created = await app.users.create({
        username: longString,
        email: "long@example.com",
        age: 30,
        profile: {
          bio: longString,
          settings: { theme: "dark", notifications: true },
        },
        tags: ["long"],
        metadata: { permissions: ["read"] },
      });

      const found = await app.users.getOne({
        where: {
          username: { eq: longString },
        },
      });

      expect(found.id).toBe(created.id);
      expect(found.username).toBe(longString);
    });

    it("should handle empty strings and nulls", async () => {
      const alepha = Alepha.create().with(AlephaOrmPostgres);
      const app = alepha.inject(App);
      await alepha.start();

      const created = await app.users.create({
        username: "",
        email: "empty@example.com",
        age: 30,
        profile: {
          bio: "",
          settings: { theme: "dark", notifications: true },
        },
        tags: [],
        metadata: { permissions: [] },
      });

      const found = await app.users.getOne({
        where: { username: { eq: "" } },
      });

      expect(found.id).toBe(created.id);
    });
  });
});
