import { z } from "zod";

import type { EventBusProtocol } from "@repo/core-shared/di";
import { userSignedUpEvent } from "../../events/user-signed-up.event";
import { AuthenticationError } from "../../entities/errors/auth";
import { cookieSchema } from "../../entities/models/cookie";
import { sessionSchema } from "../../entities/models/session";
import type { IUsersRepository } from "../repositories/users.repository.interface";
import type { IAuthenticationService } from "../services/authentication.service.interface";

// ── Input ────────────────────────────────────────────────────────────────
export const signUpInputSchema = z
  .object({
    username: z.string().min(3).max(31),
    password: z.string().min(6).max(255),
    confirmPassword: z.string().min(6).max(255),
  })
  .strict()
  .refine((d) => d.password === d.confirmPassword, {
    message: "Passwords do not match",
    path: ["confirmPassword"],
  });
export type SignUpInput = z.infer<typeof signUpInputSchema>;

// ── Output ───────────────────────────────────────────────────────────────
export const signUpOutputSchema = z.object({
  session: sessionSchema,
  cookie: cookieSchema,
});
export type SignUpOutput = z.infer<typeof signUpOutputSchema>;

// ── Use case ─────────────────────────────────────────────────────────────
export type ISignUpUseCase = ReturnType<typeof signUpUseCase>;

export const signUpUseCase =
  (
    usersRepository: IUsersRepository,
    authenticationService: IAuthenticationService,
    bus: EventBusProtocol | undefined,
  ) =>
  async (input: SignUpInput): Promise<SignUpOutput> => {
    const existingUser = await usersRepository.getUserByUsername(input.username);
    if (existingUser) {
      throw new AuthenticationError("Username taken");
    }

    const passwordHash = await authenticationService.hashPassword(input.password);
    const userId = authenticationService.generateUserId();

    const newUser = await usersRepository.createUser({
      id: userId,
      username: input.username,
      passwordHash,
    });

    const { cookie, session } = await authenticationService.createSession(newUser);

    // Auth is username-based — synthesize a deterministic email so the event
    // payload validates against userSignedUpEventSchema.email().
    // bus is optional: absent when core-events is not wired (Phase 3+).
    if (bus) {
      await bus.publish(userSignedUpEvent, {
        userId: newUser.id,
        email: `${newUser.username}@example.local`,
        signedUpAt: new Date().toISOString(),
      });
    }

    return signUpOutputSchema.parse({ session, cookie });
  };
