import { prisma, WatchlistType } from '@shieldai/db'; import { createHash } from 'crypto'; export function normalizeAddressValue(address: string): string { return address.trim().toLowerCase().replace(/\s+/g, ' '); } export function hashAddressValue(value: string): string { return createHash('sha256').update(value).digest('hex'); } const TIER_LIMITS: Record = { BASIC: 3, PLUS: 5, PREMIUM: 50, }; export class PropertyWatchlistService { async addItem( subscriptionId: string, address: string, parcelId?: string, ownerName?: string, ) { const normalized = normalizeAddressValue(address); const itemHash = hashAddressValue(normalized); const currentCount = await prisma.propertyWatchlistItem.count({ where: { subscriptionId, isActive: true }, }); const subscription = await prisma.subscription.findUnique({ where: { id: subscriptionId }, select: { tier: true }, }); if (!subscription) { throw new Error(`Subscription not found: ${subscriptionId}`); } const tier = subscription.tier.toUpperCase(); const maxItems = TIER_LIMITS[tier] ?? 3; if (currentCount >= maxItems) { throw new Error( `Property watchlist limit reached (${maxItems} items). Your ${tier} plan allows up to ${maxItems} properties.`, ); } const existing = await prisma.propertyWatchlistItem.findFirst({ where: { subscriptionId, address: normalized, isActive: true }, }); if (existing) { if (!existing.isActive) { return prisma.propertyWatchlistItem.update({ where: { id: existing.id }, data: { isActive: true }, }); } return existing; } return prisma.propertyWatchlistItem.create({ data: { subscriptionId, address: normalized, parcelId: parcelId ?? null, ownerName: ownerName ?? null, streetAddress: normalized, isActive: true, }, }); } async getItems(subscriptionId: string) { return prisma.propertyWatchlistItem.findMany({ where: { subscriptionId, isActive: true }, orderBy: { createdAt: 'desc' }, }); } async removeItem(id: string, subscriptionId: string) { const item = await prisma.propertyWatchlistItem.findFirst({ where: { id, subscriptionId, isActive: true }, }); if (!item) { throw new Error(`Watchlist item not found: ${id}`); } return prisma.propertyWatchlistItem.update({ where: { id }, data: { isActive: false }, }); } async getActiveItemsForScan(subscriptionId: string) { return prisma.propertyWatchlistItem.findMany({ where: { subscriptionId, isActive: true }, include: { snapshots: { orderBy: { capturedAt: 'desc' }, take: 1, }, }, }); } async getItemCount(subscriptionId: string) { return prisma.propertyWatchlistItem.count({ where: { subscriptionId, isActive: true }, }); } async getMaxItemsForTier(subscriptionId: string): Promise { const subscription = await prisma.subscription.findUnique({ where: { id: subscriptionId }, select: { tier: true }, }); if (!subscription) { return 3; } const tier = subscription.tier.toUpperCase(); return TIER_LIMITS[tier] ?? 3; } } export const propertyWatchlistService = new PropertyWatchlistService();