import { Alepha } from "alepha";
import { AlephaApiVerification } from "alepha/api/verifications";
import { DateTimeProvider } from "alepha/datetime";
import { AlephaEmail, MemoryEmailProvider } from "alepha/email";
import { AlephaOrmPostgres } from "alepha/orm/postgres";
import { AlephaSecurity, CryptoProvider } from "alepha/security";
import { BadRequestError, ConflictError, HttpError } from "alepha/server";
import { describe, it } from "vitest";
import {
  AlephaApiUsers,
  RealmProvider,
  RegistrationService,
  SessionService,
  UserNotifications,
  UserService,
} from "../index.ts";

const setup = async (realmSettings?: Record<string, unknown>) => {
  const alepha = Alepha.create();

  alepha.with(AlephaOrmPostgres);
  alepha.with(AlephaSecurity);
  alepha.with(AlephaEmail);
  alepha.with(AlephaApiVerification);
  alepha.with(AlephaApiUsers);
  alepha.with(UserNotifications);

  await alepha.start();

  const emailProvider = alepha.inject(MemoryEmailProvider);
  emailProvider.records = [];

  const realmProvider = alepha.inject(RealmProvider);

  // Configure realm settings if provided (applies to default realm)
  if (realmSettings) {
    realmProvider.register("default", {
      settings: realmSettings as never,
    });
  }

  return {
    alepha,
    registrationService: alepha.inject(RegistrationService),
    userService: alepha.inject(UserService),
    sessionService: alepha.inject(SessionService),
    cryptoProvider: alepha.inject(CryptoProvider),
    dateTimeProvider: alepha.inject(DateTimeProvider),
    emailProvider,
    realmProvider,
  };
};

// Helper to extract code from email
const extractCode = (emailBody: string): string => {
  const match = emailBody.match(/(\d{6})/);
  if (!match) throw new Error("Code not found in email");
  return match[1];
};

describe("alepha/api/users - RegistrationService", () => {
  describe("Phase 1: createRegistrationIntent", () => {
    it("should create a registration intent with valid data", async ({
      expect,
    }) => {
      const { registrationService } = await setup();

      const result = await registrationService.createRegistrationIntent({
        email: "newuser@example.com",
        password: "SecurePassword123!",
        username: "newuser",
        firstName: "New",
        lastName: "User",
      });

      expect(result.intentId).toBeDefined();
      expect(result.expiresAt).toBeDefined();
      expect(result.expectCaptcha).toBe(false);
      expect(result.expectEmailVerification).toBe(false);
      expect(result.expectPhoneVerification).toBe(false);
    });

    it("should create intent with email verification required when configured", async ({
      expect,
    }) => {
      const { registrationService, emailProvider, realmProvider } =
        await setup();

      // Register realm with email verification required
      realmProvider.register("verify-email-realm", {
        features: {
          notifications: true,
        },
        settings: {
          verifyEmailRequired: true,
        } as never,
      });

      const result = await registrationService.createRegistrationIntent(
        {
          email: "verify@example.com",
          password: "SecurePassword123!",
        },
        "verify-email-realm",
      );

      expect(result.intentId).toBeDefined();
      expect(result.expectEmailVerification).toBe(true);
      expect(result.expectPhoneVerification).toBe(false);
      expect(result.expectCaptcha).toBe(false);

      // Verify email was sent
      await expect.poll(() => emailProvider.records.length).toBe(1);
      expect(emailProvider.records[0].to).toBe("verify@example.com");
      expect(emailProvider.records[0].subject).toBe(
        "Verify your email address",
      );
    });

    it("should reject registration when disabled in realm settings", async ({
      expect,
    }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("no-registration-realm", {
        settings: {
          registrationAllowed: false,
        } as never,
      });

      await expect(
        registrationService.createRegistrationIntent(
          {
            email: "test@example.com",
            password: "SecurePassword123!",
          },
          "no-registration-realm",
        ),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should reject when required username is missing", async ({
      expect,
    }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("username-required-realm", {
        settings: {
          username: "required",
        } as never,
      });

      await expect(
        registrationService.createRegistrationIntent(
          {
            email: "test@example.com",
            password: "SecurePassword123!",
            // username is missing
          },
          "username-required-realm",
        ),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should reject when required email is missing", async ({ expect }) => {
      const { registrationService } = await setup();

      // Default realm requires email
      await expect(
        registrationService.createRegistrationIntent({
          username: "testuser",
          password: "SecurePassword123!",
          // email is missing
        }),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should reject when required phone is missing", async ({ expect }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("phone-required-realm", {
        settings: {
          phoneNumber: "required",
          email: "optional",
        } as never,
      });

      await expect(
        registrationService.createRegistrationIntent(
          {
            email: "test@example.com",
            password: "SecurePassword123!",
            // phoneNumber is missing
          },
          "phone-required-realm",
        ),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should reject duplicate username", async ({ expect }) => {
      const { registrationService, userService } = await setup();

      // Create existing user
      await userService.users().create({
        username: "existinguser",
        email: "existing@example.com",
        roles: ["user"],
      });

      await expect(
        registrationService.createRegistrationIntent({
          username: "existinguser",
          email: "new@example.com",
          password: "SecurePassword123!",
        }),
      ).rejects.toThrowError(ConflictError);
    });

    it("should reject duplicate email", async ({ expect }) => {
      const { registrationService, userService } = await setup();

      // Create existing user
      await userService.users().create({
        username: "existinguser",
        email: "existing@example.com",
        roles: ["user"],
      });

      await expect(
        registrationService.createRegistrationIntent({
          username: "newuser",
          email: "existing@example.com",
          password: "SecurePassword123!",
        }),
      ).rejects.toThrowError(ConflictError);
    });

    it("should reject duplicate phone number", async ({ expect }) => {
      const { registrationService, userService, realmProvider } = await setup();

      realmProvider.register("phone-realm", {
        settings: {
          phoneNumber: "optional",
          email: "optional",
        } as never,
      });

      // Create existing user with phone in the same realm
      await userService.users("phone-realm").create({
        realm: "phone-realm",
        username: "existinguser",
        email: "existing@example.com",
        phoneNumber: "+1234567890",
        roles: ["user"],
      });

      await expect(
        registrationService.createRegistrationIntent(
          {
            username: "newuser",
            email: "new@example.com",
            phoneNumber: "+1234567890",
            password: "SecurePassword123!",
          },
          "phone-realm",
        ),
      ).rejects.toThrowError(ConflictError);
    });

    it("should set correct expiration time (10 minutes)", async ({
      expect,
    }) => {
      const { registrationService, dateTimeProvider } = await setup();

      const before = dateTimeProvider.now();

      const result = await registrationService.createRegistrationIntent({
        email: "expiry@example.com",
        password: "SecurePassword123!",
      });

      const expiresAt = dateTimeProvider.of(result.expiresAt);
      const expectedExpiry = before.add(10, "minutes");

      // Should expire approximately 10 minutes from now
      expect(expiresAt.diff(expectedExpiry, "seconds")).toBeLessThan(5);
    });
  });

  describe("Phase 2: completeRegistration", () => {
    it("should complete registration without verification requirements", async ({
      expect,
    }) => {
      const { registrationService, userService } = await setup();

      // Create intent
      const intent = await registrationService.createRegistrationIntent({
        email: "complete@example.com",
        password: "SecurePassword123!",
        username: "completeuser",
        firstName: "Complete",
        lastName: "User",
      });

      // Complete registration
      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      expect(user.id).toBeDefined();
      expect(user.email).toBe("complete@example.com");
      expect(user.username).toBe("completeuser");
      expect(user.firstName).toBe("Complete");
      expect(user.lastName).toBe("User");
      expect(user.roles).toContain("user");
      expect(user.enabled).toBe(true);
    });

    it("should complete registration with valid email verification code", async ({
      expect,
    }) => {
      const { registrationService, emailProvider, realmProvider } =
        await setup();

      realmProvider.register("email-verify-realm", {
        features: {
          notifications: true,
        },
        settings: {
          verifyEmailRequired: true,
        } as never,
      });

      // Create intent (sends verification email)
      const intent = await registrationService.createRegistrationIntent(
        {
          email: "emailverify@example.com",
          password: "SecurePassword123!",
        },
        "email-verify-realm",
      );

      expect(intent.expectEmailVerification).toBe(true);

      // Extract code from email
      await expect.poll(() => emailProvider.records.length).toBe(1);
      const emailCode = extractCode(emailProvider.records[0].body);

      // Complete registration with code
      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
        emailCode,
      });

      expect(user.email).toBe("emailverify@example.com");
      expect(user.emailVerified).toBe(true);
    });

    it("should reject expired intent (410 Gone)", async ({ expect }) => {
      const { registrationService, dateTimeProvider } = await setup();

      // Create intent
      const intent = await registrationService.createRegistrationIntent({
        email: "expired@example.com",
        password: "SecurePassword123!",
      });

      // Travel forward 11 minutes (intent expires at 10)
      dateTimeProvider.travel(11, "minutes");

      // Attempt to complete
      await expect(
        registrationService.completeRegistration({
          intentId: intent.intentId,
        }),
      ).rejects.toThrow(HttpError);
    });

    it("should reject invalid intent ID", async ({ expect }) => {
      const { registrationService } = await setup();

      await expect(
        registrationService.completeRegistration({
          intentId: "550e8400-e29b-41d4-a716-446655440000",
        }),
      ).rejects.toThrow(HttpError);
    });

    it("should reject when email code is required but not provided", async ({
      expect,
    }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("email-required-realm", {
        features: {
          notifications: true,
        },
        settings: {
          verifyEmailRequired: true,
        } as never,
      });

      const intent = await registrationService.createRegistrationIntent(
        {
          email: "needscode@example.com",
          password: "SecurePassword123!",
        },
        "email-required-realm",
      );

      await expect(
        registrationService.completeRegistration({
          intentId: intent.intentId,
          // emailCode not provided
        }),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should reject invalid email verification code", async ({ expect }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("email-verify-realm", {
        features: {
          notifications: true,
        },
        settings: {
          verifyEmailRequired: true,
        } as never,
      });

      const intent = await registrationService.createRegistrationIntent(
        {
          email: "wrongcode@example.com",
          password: "SecurePassword123!",
        },
        "email-verify-realm",
      );

      await expect(
        registrationService.completeRegistration({
          intentId: intent.intentId,
          emailCode: "000000", // Wrong code
        }),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should not allow intent reuse after successful registration", async ({
      expect,
    }) => {
      const { registrationService } = await setup();

      const intent = await registrationService.createRegistrationIntent({
        email: "onetime@example.com",
        password: "SecurePassword123!",
      });

      // First completion should succeed
      await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      // Second attempt should fail (intent deleted)
      await expect(
        registrationService.completeRegistration({
          intentId: intent.intentId,
        }),
      ).rejects.toThrow(HttpError);
    });

    it("should detect race condition when email is taken during verification", async ({
      expect,
    }) => {
      const { registrationService, userService, realmProvider } = await setup();

      realmProvider.register("race-realm", {
        settings: {
          verifyEmailRequired: false,
        } as never,
      });

      // Create intent
      const intent = await registrationService.createRegistrationIntent(
        {
          email: "race@example.com",
          password: "SecurePassword123!",
        },
        "race-realm",
      );

      // Simulate another user registering with same email while verification pending
      await userService.users("race-realm").create({
        realm: "race-realm",
        email: "race@example.com",
        username: "racewinner",
        roles: ["user"],
      });

      // Attempt to complete should fail
      await expect(
        registrationService.completeRegistration({
          intentId: intent.intentId,
        }),
      ).rejects.toThrowError(ConflictError);
    });

    it("should create credentials identity with hashed password", async ({
      expect,
    }) => {
      const { registrationService, sessionService, realmProvider } =
        await setup();

      const intent = await registrationService.createRegistrationIntent({
        email: "withpassword@example.com",
        password: "SecurePassword123!",
      });

      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      // Verify login works with the password
      const loggedInUser = await sessionService.login(
        "credentials",
        "withpassword@example.com",
        "SecurePassword123!",
      );

      expect(loggedInUser?.id).toBe(user.id);
    });

    it("should allow login with username after registration", async ({
      expect,
    }) => {
      const { registrationService, sessionService } = await setup({
        username: "optional",
      });

      const intent = await registrationService.createRegistrationIntent({
        email: "logintest@example.com",
        username: "loginuser",
        password: "SecurePassword123!",
      });

      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      // Verify login works with username
      const loggedInUser = await sessionService.login(
        "credentials",
        "loginuser",
        "SecurePassword123!",
      );

      expect(loggedInUser?.id).toBe(user.id);
    });
  });

  describe("Full registration flow integration", () => {
    it("should complete full registration flow without verification", async ({
      expect,
    }) => {
      const { registrationService, sessionService } = await setup({
        username: "optional",
      });

      // Phase 1: Create intent
      const intent = await registrationService.createRegistrationIntent({
        email: "fullflow@example.com",
        username: "fullflowuser",
        password: "SecurePassword123!",
        firstName: "Full",
        lastName: "Flow",
      });

      expect(intent.expectEmailVerification).toBe(false);
      expect(intent.expectPhoneVerification).toBe(false);

      // Phase 2: Complete registration
      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      expect(user.email).toBe("fullflow@example.com");
      expect(user.username).toBe("fullflowuser");

      // Verify user can login
      const session = await sessionService.login(
        "credentials",
        "fullflowuser",
        "SecurePassword123!",
      );
      expect(session?.id).toBe(user.id);
    });

    it("should complete full registration flow with email verification", async ({
      expect,
    }) => {
      const {
        registrationService,
        sessionService,
        emailProvider,
        realmProvider,
      } = await setup();

      realmProvider.register("full-verify-realm", {
        features: {
          notifications: true,
        },
        settings: {
          verifyEmailRequired: true,
        } as never,
      });

      // Phase 1: Create intent
      const intent = await registrationService.createRegistrationIntent(
        {
          email: "fullverify@example.com",
          password: "SecurePassword123!",
        },
        "full-verify-realm",
      );

      expect(intent.expectEmailVerification).toBe(true);

      // Extract verification code
      await expect.poll(() => emailProvider.records.length).toBe(1);
      const emailCode = extractCode(emailProvider.records[0].body);

      // Phase 2: Complete registration with code
      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
        emailCode,
      });

      expect(user.email).toBe("fullverify@example.com");
      expect(user.emailVerified).toBe(true);

      // Verify user can login
      const session = await sessionService.login(
        "credentials",
        "fullverify@example.com",
        "SecurePassword123!",
        "full-verify-realm",
      );
      expect(session?.id).toBe(user.id);
    });
  });

  describe("Password policy enforcement during registration", () => {
    it("should reject registration when password violates realm policy", async ({
      expect,
    }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("strict-policy-realm", {
        settings: {
          passwordPolicy: {
            minLength: 10,
            requireUppercase: true,
            requireLowercase: false,
            requireNumbers: false,
            requireSpecialCharacters: false,
          },
        } as never,
      });

      await expect(
        registrationService.createRegistrationIntent(
          {
            email: "weakpass@example.com",
            password: "shortpw!",
          },
          "strict-policy-realm",
        ),
      ).rejects.toThrowError(BadRequestError);
    });

    it("should accept registration when password meets realm policy", async ({
      expect,
    }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("strict-policy-realm", {
        settings: {
          passwordPolicy: {
            minLength: 10,
            requireUppercase: true,
            requireLowercase: false,
            requireNumbers: false,
            requireSpecialCharacters: false,
          },
        } as never,
      });

      const result = await registrationService.createRegistrationIntent(
        {
          email: "strongpass@example.com",
          password: "StrongPass123",
        },
        "strict-policy-realm",
      );

      expect(result.intentId).toBeDefined();
    });
  });

  describe("Registration rate limiting", () => {
    it("should rate limit registration attempts by IP", async ({ expect }) => {
      const { alepha, registrationService } = await setup();

      await alepha.fork(async () => {
        alepha.store.set("alepha.http.request", { ip: "10.0.0.1" } as never);

        // Make 10 registration attempts (they may fail for duplicate email, that's fine)
        for (let i = 0; i < 10; i++) {
          await registrationService
            .createRegistrationIntent({
              email: `ratelimit-${i}@example.com`,
              password: "SecurePassword123!",
            })
            .catch(() => {});
        }

        // 11th attempt should be rate limited
        await expect(
          registrationService.createRegistrationIntent({
            email: "ratelimit-overflow@example.com",
            password: "SecurePassword123!",
          }),
        ).rejects.toThrowError(BadRequestError);
      });
    });
  });

  describe("Default roles from realm settings", () => {
    it("should assign defaultRoles from realm settings to new users", async ({
      expect,
    }) => {
      const { registrationService, realmProvider } = await setup();

      realmProvider.register("custom-roles-realm", {
        settings: {
          defaultRoles: ["member", "viewer"],
        } as never,
      });

      const intent = await registrationService.createRegistrationIntent(
        {
          email: "roleuser@example.com",
          password: "SecurePassword123!",
        },
        "custom-roles-realm",
      );

      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      expect(user.roles).toEqual(["member", "viewer"]);
    });
  });

  describe("Cross-realm email uniqueness", () => {
    it("should allow same email in different realms", async ({ expect }) => {
      const { registrationService, userService, realmProvider } = await setup();

      realmProvider.register("realm-a");
      realmProvider.register("realm-b");

      // Create a user with this email in realm-a directly
      await userService.users("realm-a").create({
        realm: "realm-a",
        email: "shared@example.com",
        roles: ["user"],
      });

      // Register a new user with the same email in realm-b via registration flow
      const intent = await registrationService.createRegistrationIntent(
        {
          email: "shared@example.com",
          password: "SecurePassword123!",
        },
        "realm-b",
      );

      const user = await registrationService.completeRegistration({
        intentId: intent.intentId,
      });

      expect(user.email).toBe("shared@example.com");
    });
  });
});
