import { and, eq } from 'drizzle-orm';
import { bytesToHex } from '@/lib/encoding';
import { z } from 'zod';
import type { DrizzleClient } from '@/server/db/client';
import { emailVerifications, shippingAddresses, users } from '@/server/db/schema';
import { encrypt } from '@/server/db/crypto';
import type { FactoryOperationHandler, FactoryStore } from './core';
import { createFactoryMailbox, deleteFactoryMailbox, readFactoryMailbox } from './mail-capture';

const uuid = z.uuid();
const actorProfileInput = z.object({
  actorId: uuid,
  displayName: z.string().min(1).max(80).optional(),
  city: z.string().min(1).max(120).optional(),
  emailVerified: z.boolean().optional(),
});
const actorStateInput = z.object({
  actorId: uuid,
  accountState: z.enum(['ACTIVE', 'FROZEN', 'DELETED_PENDING']),
});
const verificationInput = z.object({
  key: uuid,
  actorId: uuid,
  rawToken: z.string().min(32).max(256),
  email: z.email(),
  purpose: z.enum(['signup', 'change']).default('signup'),
  expiresAt: z.iso.datetime(),
});
const actorAddressInput = z.object({
  actorId: uuid,
  recipientName: z.string().min(1).max(120),
  cityCode: z.string().min(1).max(40),
  cityName: z.string().min(1).max(120),
});
const mailboxInput = z.object({ key: uuid, recipient: z.email() });

export function actorAddressValues(raw: z.input<typeof actorAddressInput>, piiKey: string) {
  const parsed = actorAddressInput.parse(raw);
  return {
    userId: parsed.actorId,
    recipientName: encrypt(parsed.recipientName, piiKey),
    recipientPhone: encrypt('+972500000000', piiKey),
    cityCode: parsed.cityCode,
    cityName: parsed.cityName,
    streetName: 'Factory Street',
    houseNumber: '1',
    zip: '6100001',
    isDefault: true,
  };
}

async function sha256(value: string): Promise<string> {
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
  return bytesToHex(digest);
}

async function requireActor(store: FactoryStore, runId: string, actorId: string): Promise<void> {
  if (!(await store.findOwned(runId, 'actor', actorId))) {
    throw new Error('actor is not owned by factory run');
  }
}

export function createActorProfileFactoryHandlers(input: {
  db: DrizzleClient;
  store: FactoryStore;
  piiKey: string;
}): Record<string, FactoryOperationHandler> {
  const { db, store, piiKey } = input;
  return {
    createMailCapture: {
      dependencyOrder: 90,
      reserve: (raw) => ({ kind: 'mail-capture', id: mailboxInput.parse(raw).key }),
      async execute({ runId, input: raw }) {
        const parsed = mailboxInput.parse(raw);
        createFactoryMailbox(runId, parsed.key, parsed.recipient);
        return {
          entity: { kind: 'mail-capture', id: parsed.key },
          result: { mailboxId: parsed.key, recipient: parsed.recipient, runId },
        };
      },
      async read({ runId, entity }) {
        const mailbox = readFactoryMailbox(runId, entity.entityId);
        if (!mailbox) throw new Error('Factory mailbox missing');
        return mailbox;
      },
      async cleanup({ runId, entity }) {
        deleteFactoryMailbox(runId, entity.entityId);
      },
    },

    setActorAccountState: {
      dependencyOrder: 11,
      async execute({ runId, input: raw }) {
        const parsed = actorStateInput.parse(raw);
        await requireActor(store, runId, parsed.actorId);
        const [updated] = await db
          .update(users)
          .set({ accountState: parsed.accountState })
          .where(eq(users.id, parsed.actorId))
          .returning({ actorId: users.id, accountState: users.accountState });
        if (!updated) throw new Error('Factory actor missing');
        return { result: { ...updated, runId } };
      },
      async read() {
        throw new Error('setActorAccountState does not own an entity');
      },
      async cleanup() {
        throw new Error('setActorAccountState does not own an entity');
      },
    },

    updateActorProfile: {
      dependencyOrder: 11,
      async execute({ runId, input: raw }) {
        const parsed = actorProfileInput.parse(raw);
        await requireActor(store, runId, parsed.actorId);
        const [updated] = await db
          .update(users)
          .set({
            ...(parsed.displayName === undefined ? {} : { displayName: parsed.displayName }),
            ...(parsed.city === undefined ? {} : { city: parsed.city }),
            ...(parsed.emailVerified === undefined
              ? {}
              : { emailVerifiedAt: parsed.emailVerified ? new Date() : null }),
          })
          .where(eq(users.id, parsed.actorId))
          .returning({ actorId: users.id });
        if (!updated) throw new Error('Factory actor missing');
        return { result: { actorId: updated.actorId, runId } };
      },
      async read() {
        throw new Error('updateActorProfile does not own an entity');
      },
      async cleanup() {
        throw new Error('updateActorProfile does not own an entity');
      },
    },

    createActorAddress: {
      dependencyOrder: 11,
      async execute({ runId, input: raw }) {
        const parsed = actorAddressInput.parse(raw);
        await requireActor(store, runId, parsed.actorId);
        const [address] = await db
          .insert(shippingAddresses)
          .values(actorAddressValues(parsed, piiKey))
          .returning({ addressId: shippingAddresses.id });
        if (!address) throw new Error('Factory actor address missing');
        return { result: { addressId: address.addressId, actorId: parsed.actorId, runId } };
      },
      async read() {
        throw new Error('createActorAddress does not own an entity');
      },
      async cleanup() {
        throw new Error('createActorAddress does not own an entity');
      },
    },

    createEmailVerification: {
      dependencyOrder: 15,
      reserve: (raw) => ({ kind: 'email-verification', id: verificationInput.parse(raw).key }),
      async execute({ runId, input: raw }) {
        const parsed = verificationInput.parse(raw);
        await requireActor(store, runId, parsed.actorId);
        await db.insert(emailVerifications).values({
          id: parsed.key,
          userId: parsed.actorId,
          tokenHash: await sha256(parsed.rawToken),
          emailEncrypted: encrypt(parsed.email.toLowerCase(), piiKey),
          purpose: parsed.purpose,
          expiresAt: new Date(parsed.expiresAt),
        });
        return {
          entity: { kind: 'email-verification', id: parsed.key },
          result: {
            verificationId: parsed.key,
            actorId: parsed.actorId,
            rawToken: parsed.rawToken,
            runId,
          },
        };
      },
      async read({ entity }) {
        const [row] = await db
          .select({
            verificationId: emailVerifications.id,
            actorId: emailVerifications.userId,
            purpose: emailVerifications.purpose,
            expiresAt: emailVerifications.expiresAt,
            consumedAt: emailVerifications.consumedAt,
          })
          .from(emailVerifications)
          .where(and(eq(emailVerifications.id, entity.entityId)))
          .limit(1);
        if (!row) throw new Error('Factory email verification missing');
        return {
          ...row,
          expiresAt: row.expiresAt.toISOString(),
          consumedAt: row.consumedAt?.toISOString() ?? null,
        };
      },
      async cleanup({ entity }) {
        await db.delete(emailVerifications).where(eq(emailVerifications.id, entity.entityId));
      },
    },
  };
}
