import { z } from "zod";

import type {
  EventBusProtocol,
  ConsentFactoryProtocol,
} 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";

// Cookie name written by the anonymous consent banner (mirrors CONSENT_COOKIE_NAME in @repo/core-consent).
const ANONYMOUS_CONSENT_COOKIE = "cc_consent";

function extractConsentFromCookieHeader(cookieHeader: string): string[] | null {
  for (const part of cookieHeader.split(";")) {
    const eqIdx = part.indexOf("=");
    if (eqIdx === -1) continue;
    const name = part.slice(0, eqIdx).trim();
    if (name !== ANONYMOUS_CONSENT_COOKIE) continue;
    const value = part.slice(eqIdx + 1).trim();
    const cats = value
      .split(",")
      .map((c) => c.trim())
      .filter(Boolean);
    return cats.length > 0 ? cats : null;
  }
  return null;
}

// ── 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),
    cookieHeader: z.string().optional(),
  })
  .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,
  clearCookie: cookieSchema.optional(),
});
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,
    consentFactory: ConsentFactoryProtocol | 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.
    if (bus) {
      await bus.publish(userSignedUpEvent, {
        userId: newUser.id,
        email: `${newUser.username}@example.local`,
        signedUpAt: new Date().toISOString(),
      });
    }

    // Migrate anonymous consent when both a cookie header and a consent factory
    // are present. consentFactory is optional: absent when core-consent is not wired.
    const cookieState = input.cookieHeader
      ? extractConsentFromCookieHeader(input.cookieHeader)
      : null;

    let clearCookie: z.infer<typeof cookieSchema> | undefined;
    if (cookieState && consentFactory) {
      const consent = await consentFactory(newUser.id);
      for (const category of cookieState) {
        await consent.grant(category, { method: "signup-migration" });
      }
      clearCookie = {
        name: ANONYMOUS_CONSENT_COOKIE,
        value: "",
        attributes: { maxAge: 0, path: "/" },
      };
    }

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