import { createHash, randomBytes, randomUUID } from "node:crypto";
import { and, eq } from "drizzle-orm";
import type { Database } from "@app/database";
import { schema } from "@app/database";
import type { EventType } from "@app/shared-types";
import type { NewDomainEvent } from "@app/events";
import { errors } from "./errors.js";

/** Slumpad, läsbar inbjudningskod utan lättförväxlade tecken. */
export function generateInviteCode(): string {
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  const bytes = randomBytes(8);
  let code = "";
  for (const b of bytes) code += alphabet[b % alphabet.length];
  return code;
}

export function sha256(input: string): string {
  return createHash("sha256").update(input).digest("hex");
}

export function todayIso(): string {
  return new Date().toISOString().slice(0, 10);
}

export function currentMonth(): string {
  return new Date().toISOString().slice(0, 7);
}

/** Verifiera hushållsmedlemskap – enda vägen till hushållsdata (spec §56). */
export async function requireMembership(
  db: Database,
  householdId: string,
  userId: string,
): Promise<{ role: string; portionFactor: number }> {
  const [member] = await db
    .select()
    .from(schema.householdMembers)
    .where(
      and(
        eq(schema.householdMembers.householdId, householdId),
        eq(schema.householdMembers.userId, userId),
      ),
    )
    .limit(1);
  if (!member) throw errors.forbidden("Du är inte medlem i det här hushållet.");
  return { role: member.role, portionFactor: member.portionFactor };
}

/** Hämta användarens aktiva hushåll (första medlemskapet) eller null. */
export async function getActiveHouseholdId(db: Database, userId: string): Promise<string | null> {
  const [member] = await db
    .select({ householdId: schema.householdMembers.householdId })
    .from(schema.householdMembers)
    .where(eq(schema.householdMembers.userId, userId))
    .orderBy(schema.householdMembers.joinedAt)
    .limit(1);
  return member?.householdId ?? null;
}

/** Som ovan men kastar 404 om användaren saknar hushåll. */
export async function requireActiveHousehold(db: Database, userId: string): Promise<string> {
  const id = await getActiveHouseholdId(db, userId);
  if (!id) {
    throw errors.notFound("Du har inget hushåll ännu. Skapa ett under Hemma → Hushåll.");
  }
  return id;
}

/** Skriv domänhändelse till outboxen (spec §55) – i samma transaktion när tx skickas in. */
export async function emitEvent<T extends EventType>(
  db: Database,
  event: NewDomainEvent<T>,
): Promise<void> {
  await db.insert(schema.domainEvents).values({
    type: event.type,
    userId: event.userId ?? null,
    householdId: event.householdId ?? null,
    payload: event.payload as Record<string, unknown>,
    correlationId: event.correlationId ?? null,
  });
}

/** Audit-logg (spec §56). */
export async function audit(
  db: Database,
  entry: {
    actorUserId?: string | undefined;
    actorType?: "user" | "admin" | "system" | "worker";
    action: string;
    targetType?: string;
    targetId?: string;
    metadata?: Record<string, unknown>;
    ip?: string | undefined;
    correlationId?: string | undefined;
  },
): Promise<void> {
  await db.insert(schema.auditLogs).values({
    actorUserId: entry.actorUserId ?? null,
    actorType: entry.actorType ?? "user",
    action: entry.action,
    targetType: entry.targetType ?? null,
    targetId: entry.targetId ?? null,
    metadata: entry.metadata ?? null,
    ip: entry.ip ?? null,
    correlationId: entry.correlationId ?? null,
  });
}

export function newCorrelationId(): string {
  return randomUUID();
}
