import { HttpError } from "@batch-cooking/error-tools";
import {
  ErrorCode,
  type LoginInput,
  type SafeUserProfile,
  type SignupInput,
} from "@batch-cooking/shared";
import argon2 from "argon2";
import { env } from "../../config/env.js";
import { prisma } from "../../db/prisma.js";
import { analytics } from "../../lib/analytics.service.js";
import { signAuthToken } from "../../lib/jwt.js";
import { toSafeProfile } from "../../lib/safe-profile.js";
import { leaveCurrentHouse } from "../house/house.service.js";

/** Result of a successful signup/login: the safe profile plus the signed session JWT to set as a cookie. */
interface AuthResult {
  /** The authenticated profile, safe to hand back to the client. */
  profile: SafeUserProfile;
  /** Signed session JWT — the caller sets this as the session cookie's value. */
  token: string;
}

// argon2's defaults (64 MB memory, 3 passes) are deliberately expensive —
// that's the point, for real passwords. In tests we hash/verify dozens of
// times per run against throwaway data, so a much cheaper cost keeps the
// suite fast without weakening anything real. Never applies outside
// NODE_ENV=test.
const testHashOptions = { memoryCost: 8192, timeCost: 2, parallelism: 1 };
const hashOptions = env.NODE_ENV === "test" ? testHashOptions : undefined;

/**
 * Creates a profile (`user_profiles`), hashes the password, and issues a
 * session token.
 *
 * @throws {HttpError} `409 EMAIL_ALREADY_IN_USE` if the email is already taken.
 */
export async function signup(input: SignupInput): Promise<AuthResult> {
  try {
    const existing = await prisma.userProfile.findUnique({
      where: { email: input.email },
    });
    if (existing) {
      throw new HttpError(409, ErrorCode.EMAIL_ALREADY_IN_USE, "Email already in use");
    }

    const passwordHash = await argon2.hash(input.password, hashOptions);

    // No household is created here — it's now an optional step of the
    // onboarding wizard (create or join one, or skip — see
    // `house.service.ts`'s `createHouse`/`joinHouse`), not an implicit side
    // effect of signing up. `houseId` starts out `null`, same as `dietId`.
    const profile = await prisma.userProfile.create({
      data: {
        firstName: input.firstName,
        lastName: input.lastName,
        email: input.email,
        passwordHash,
      },
    });

    analytics.recordEvent("user.signup", { actorId: profile.id });

    const token = signAuthToken({
      userProfileId: profile.id,
      tokenVersion: profile.tokenVersion,
    });
    return { profile: toSafeProfile(profile), token };
  } catch (err) {
    // Rethrown as-is — `wrapAsyncHandler`/the error middleware (which already
    // logs it, see `error-logger.ts`) is what actually handles it, this
    // service layer just isn't allowed a bare `await` per the repo's
    // async/try-catch convention.
    throw err;
  }
}

/**
 * Permanently deletes a profile, after re-verifying its password —
 * deleting an account is irreversible, so it's gated behind proving the
 * caller still is who the session says they are, same spirit as the
 * password check in {@link login}.
 *
 * If the profile administers a household with other members, adminship is
 * handed off before the profile is deleted (see `house.service.ts`'s
 * `leaveCurrentHouse`); if it's the household's last member, the household
 * itself is deleted along with it. `UserProfileAllergy` rows cascade via
 * the schema's `onDelete: Cascade`.
 *
 * @throws {HttpError} `401 INVALID_CREDENTIALS` if the password is wrong.
 */
export async function deleteAccount(profileId: number, password: string): Promise<void> {
  try {
    const profile = await prisma.userProfile.findUnique({
      where: { id: profileId },
    });
    if (!profile || !(await argon2.verify(profile.passwordHash, password))) {
      throw new HttpError(401, ErrorCode.INVALID_CREDENTIALS, "Invalid password");
    }

    if (profile.houseId !== null) {
      await leaveCurrentHouse(profile.id, profile.houseId);
    }
    await prisma.userProfile.delete({ where: { id: profile.id } });
  } catch (err) {
    throw err; // see signup()'s catch comment above
  }
}

/**
 * Verifies credentials and issues a fresh session token.
 *
 * @throws {HttpError} `401 INVALID_CREDENTIALS` for either an unknown email
 *   or a wrong password — deliberately the same error either way, so a
 *   caller can never learn whether a given email has an account.
 */
export async function login(input: LoginInput): Promise<AuthResult> {
  try {
    const profile = await prisma.userProfile.findUnique({
      where: { email: input.email },
    });

    if (!profile || !(await argon2.verify(profile.passwordHash, input.password))) {
      throw new HttpError(401, ErrorCode.INVALID_CREDENTIALS, "Invalid email or password");
    }

    const token = signAuthToken({
      userProfileId: profile.id,
      tokenVersion: profile.tokenVersion,
    });
    return { profile: toSafeProfile(profile), token };
  } catch (err) {
    throw err; // see signup()'s catch comment above
  }
}
