import type { SessionCookie } from "../routes/auth/callback.ts"; import type { Lease, LeaseDocument, MaintenanceRequest, Message, PaymentTransaction, Property, PropertyMedia, RentSchedule, TenantApplication, TenantInvite, User, } from "../db/interfaces.ts"; import type { PropertyMergeResult } from "./propertyMergeService.ts"; import type { PaymentMethod } from "./paymentService.ts"; import type { MaintenanceData } from "./maintenanceRequestService.ts"; const E2E_ENABLED = Deno.env.get("E2E_TEST") === "true"; const BASE_TIME = new Date("2024-01-15T12:00:00.000Z").getTime(); const e2eUserIds = { landlord: "e2e-landlord-1", "landlord-onboarding": "e2e-landlord-setup-1", tenant: "e2e-tenant-1", }; let e2eUsers: Record = { [e2eUserIds.landlord]: { id: e2eUserIds.landlord, name: "Alex Landlord", email: "alex.landlord@example.com", phone: "555-0100", address_1: "88 Beacon Way", address_2: "", city: "Seattle", state: "WA", zip: "98101", country: "USA", user_type: "landlord", onboarded_landlord: true, onboarded_tenant: false, email_verified: true, banking_setup_complete: true, onboarding_step: 0, encrypted_data: null, started_on: BASE_TIME - 1000 * 60 * 60 * 24, updated_on: BASE_TIME, }, [e2eUserIds["landlord-onboarding"]]: { id: e2eUserIds["landlord-onboarding"], name: "", email: "", phone: "", address_1: "", address_2: "", city: "", state: "", zip: "", country: "", user_type: "landlord", onboarded_landlord: false, onboarded_tenant: false, email_verified: false, banking_setup_complete: false, onboarding_step: 1, encrypted_data: null, started_on: BASE_TIME - 1000 * 60 * 60 * 48, updated_on: BASE_TIME, }, [e2eUserIds.tenant]: { id: e2eUserIds.tenant, name: "Taylor Tenant", email: "taylor.tenant@example.com", phone: "555-0199", address_1: "402 Pine St", address_2: "Unit 12", city: "Seattle", state: "WA", zip: "98102", country: "USA", user_type: "tenant", onboarded_landlord: false, onboarded_tenant: true, email_verified: true, banking_setup_complete: false, onboarding_step: 0, encrypted_data: null, started_on: BASE_TIME - 1000 * 60 * 60 * 24, updated_on: BASE_TIME, }, }; let e2eProperties: Property[] = [ { id: "e2e-prop-1", title: "Skyline Lofts", description: "Modern loft with skyline views.", address_1: "123 Harbor Ave", address_2: "", city: "Seattle", state: "WA", zip: "98109", country: "USA", bedrooms: 2, bathrooms: 2, square_footage: 980, year_built: 2018, property_type: "apartment", listing_status: "active", rental_status: "available", monthly_rent: 2400, security_deposit: 2400, lease_duration_months: 12, amenities: "Rooftop deck, gym", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 14, updated_on: BASE_TIME - 1000 * 60 * 60 * 24 * 3, owner_id: e2eUserIds.landlord, }, { id: "e2e-prop-2", title: "Maple Row", description: "Cozy townhome near the park.", address_1: "456 Maple St", address_2: "", city: "Seattle", state: "WA", zip: "98122", country: "USA", bedrooms: 3, bathrooms: 2, square_footage: 1250, year_built: 2012, property_type: "townhome", listing_status: "active", rental_status: "occupied", monthly_rent: 1800, security_deposit: 1800, lease_duration_months: 12, amenities: "Garage, patio", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 30, updated_on: BASE_TIME - 1000 * 60 * 60 * 24 * 1, owner_id: e2eUserIds.landlord, }, ]; let e2eLeases: Lease[] = [ { id: "e2e-lease-1", title: "Maple Row Lease", property_id: "e2e-prop-2", property_address: "456 Maple St, Seattle, WA", tenant_id: e2eUserIds.tenant, owner_id: e2eUserIds.landlord, start_date: BASE_TIME - 1000 * 60 * 60 * 24 * 120, end_date: BASE_TIME + 1000 * 60 * 60 * 24 * 245, rent: 1800, security_deposit: 1800, lease_status: "active", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 120, updated_on: BASE_TIME - 1000 * 60 * 60 * 24 * 5, }, ]; const extraE2EMessages: Message[] = Array.from({ length: 9 }, (_, index) => ({ id: `e2e-msg-${index + 3}`, lease_id: "e2e-lease-1", sender_id: e2eUserIds.landlord, recipient_id: e2eUserIds.tenant, message_type: "general", subject: `Lease update ${index + 1}`, content: `Follow-up note ${index + 1} about the lease timeline.`, is_read: true, created_on: BASE_TIME - 1000 * 60 * 60 * (4 + index), })); let e2eMessages: Message[] = [ { id: "e2e-msg-1", lease_id: "e2e-lease-1", sender_id: e2eUserIds.tenant, recipient_id: e2eUserIds.landlord, message_type: "maintenance", subject: "Leaking kitchen faucet", content: "The kitchen faucet drips after turning it off.", is_read: false, created_on: BASE_TIME - 1000 * 60 * 60 * 3, }, { id: "e2e-msg-2", lease_id: "e2e-lease-1", sender_id: e2eUserIds.landlord, recipient_id: e2eUserIds.tenant, message_type: "notice", subject: "Inspection scheduled", content: "Annual inspection will take place next Tuesday.", is_read: true, created_on: BASE_TIME - 1000 * 60 * 60 * 26, }, ...extraE2EMessages, ]; export function addE2EMessage( leaseId: string | null, senderId: string, recipientId: string, messageType: string, subject: string, content: string, parentMessageId: string | null = null, attachmentsJson: string | null = null, ) { const newMessage: Message = { id: crypto.randomUUID(), lease_id: leaseId ?? null, sender_id: senderId, recipient_id: recipientId, message_type: messageType, subject, content, content_encrypted: content, parent_message_id: parentMessageId, attachments_json: attachmentsJson ? JSON.parse(attachmentsJson) as Record : null, is_read: false, created_on: BASE_TIME, }; e2eMessages = [newMessage, ...e2eMessages]; return newMessage; } export function getE2EMessageById(messageId: string): Message | null { return e2eMessages.find((message) => message.id === messageId) ?? null; } export function getE2EMessageThread(messageId: string): Message[] | null { const initialMessage = getE2EMessageById(messageId); if (!initialMessage) return null; const rootId = initialMessage.parent_message_id || initialMessage.id; return e2eMessages .filter((message) => message.id === rootId || message.parent_message_id === rootId ) .sort((a, b) => (a.created_on ?? 0) - (b.created_on ?? 0)); } export function getE2EMessagesByLease(leaseId: string) { return e2eMessages.filter((message) => message.lease_id === leaseId); } let e2eMaintenanceRequests: MaintenanceRequest[] = [ { id: "e2e-maint-1", property_id: "e2e-prop-2", tenant_id: e2eUserIds.tenant, request_type: "plumbing", description: "Kitchen sink is leaking.", status: "open", priority: "high", created_on: BASE_TIME - 1000 * 60 * 60 * 12, resolved_on: null, }, { id: "e2e-maint-2", property_id: "e2e-prop-2", tenant_id: e2eUserIds.tenant, request_type: "electrical", description: "Living room outlet not working.", status: "in_progress", priority: "medium", created_on: BASE_TIME - 1000 * 60 * 60 * 72, resolved_on: null, }, ]; let e2eTenantApplications: TenantApplication[] = [ { id: "e2e-app-1", property_id: "e2e-prop-1", applicant_id: e2eUserIds.tenant, application_status: "pending", application_data_encrypted: '{"source":"e2e"}', submitted_on: BASE_TIME - 1000 * 60 * 60 * 6, reviewed_on: null, }, ]; let e2eTenantInvites: TenantInvite[] = []; let e2eRentSchedules: RentSchedule[] = []; let e2ePropertyMedia: PropertyMedia[] = [ { id: "e2e-media-1", property_id: "e2e-prop-1", media_type: "photo", file_url: "/logo.svg", file_name: "Skyline Lofts", display_order: 0, created_on: BASE_TIME, }, { id: "e2e-media-2", property_id: "e2e-prop-1", media_type: "photo", file_url: "/logo.svg", file_name: "Skyline Lofts Interior", display_order: 1, created_on: BASE_TIME, }, ]; let e2ePaymentMethods: PaymentMethod[] = [ { id: "e2e-pay-method-1", user_id: e2eUserIds.tenant, bank_name: "Northwest Credit Union", method_type: "bank_account", account_type: "checking", routing_number_encrypted: "test-routing", account_number_encrypted: "test-account", is_default: true, created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 10, updated_on: BASE_TIME - 1000 * 60 * 60 * 24 * 2, }, ]; let e2ePaymentTransactions: PaymentTransaction[] = [ { id: "e2e-pay-0", lease_id: "e2e-lease-1", amount: 1800, payment_date: BASE_TIME - 1000 * 60 * 60 * 24 * 10, payment_method: "ach", transaction_status: "completed", stripe_payment_id: "pi_test_jan_1", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 10, }, { id: "e2e-pay-late-1", lease_id: "e2e-lease-1", amount: 1800, payment_date: BASE_TIME - 1000 * 60 * 60 * 24 * 7, payment_method: "ach", transaction_status: "pending", stripe_payment_id: "pi_test_late_1", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 7, }, { id: "e2e-pay-1", lease_id: "e2e-lease-1", amount: 1800, payment_date: BASE_TIME - 1000 * 60 * 60 * 24 * 25, payment_method: "ach", transaction_status: "completed", stripe_payment_id: "pi_test_123", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 25, }, { id: "e2e-pay-2", lease_id: "e2e-lease-1", amount: 1800, payment_date: BASE_TIME - 1000 * 60 * 60 * 24 * 55, payment_method: "ach", transaction_status: "completed", stripe_payment_id: "pi_test_456", created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 55, }, ]; let e2eLeaseDocuments: LeaseDocument[] = [ { id: "e2e-doc-lease-1", lease_id: "e2e-lease-1", document_type: "lease", file_url: "/static/sample-lease.pdf", file_name: "maple-row-lease.pdf", signed_by_landlord: false, signed_by_tenant: false, signature_date: null, created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 20, }, { id: "e2e-doc-receipt-1", lease_id: "e2e-lease-1", document_type: "receipt", file_url: "/static/sample-receipt.pdf", file_name: "rent-receipt-jan.pdf", signed_by_landlord: true, signed_by_tenant: true, signature_date: BASE_TIME - 1000 * 60 * 60 * 24 * 15, created_on: BASE_TIME - 1000 * 60 * 60 * 24 * 15, }, ]; export function getE2ENow() { return BASE_TIME; } export function addE2EPaymentMethod( userId: string, input: Partial, ) { const now = BASE_TIME; const isDefault = input.is_default ?? false; if (isDefault) { e2ePaymentMethods = e2ePaymentMethods.map((method) => method.user_id === userId ? { ...method, is_default: false } : method ); } const newMethod: PaymentMethod = { id: crypto.randomUUID(), user_id: userId, bank_name: input.bank_name ?? null, method_type: input.method_type ?? "bank_account", account_type: input.account_type ?? null, routing_number_encrypted: input.routing_number_encrypted ?? null, account_number_encrypted: input.account_number_encrypted ?? null, is_default: isDefault, created_on: now, updated_on: now, }; e2ePaymentMethods = [...e2ePaymentMethods, newMethod]; return newMethod; } export function updateE2EPaymentMethod( id: string, updates: Partial, ) { let updated: PaymentMethod | null = null; e2ePaymentMethods = e2ePaymentMethods.map((method) => { if (method.id !== id) return method; updated = { ...method, ...updates, updated_on: BASE_TIME }; return updated; }); return updated; } export function deleteE2EPaymentMethod(id: string) { const existing = e2ePaymentMethods.find((method) => method.id === id) || null; e2ePaymentMethods = e2ePaymentMethods.filter((method) => method.id !== id); return existing; } export function getE2EDefaultPaymentMethod(userId: string) { return e2ePaymentMethods.find((method) => method.user_id === userId && method.is_default ) || null; } export function addE2EPaymentTransaction( leaseId: string, amount: number, paymentDate: number, paymentMethod: string, transactionStatus: string, stripePaymentId?: string, ) { const newTransaction: PaymentTransaction = { id: crypto.randomUUID(), lease_id: leaseId, amount, payment_date: paymentDate, payment_method: paymentMethod, transaction_status: transactionStatus, stripe_payment_id: stripePaymentId ?? null, created_on: BASE_TIME, }; e2ePaymentTransactions = [newTransaction, ...e2ePaymentTransactions]; return newTransaction; } export function isE2EEnabled() { return E2E_ENABLED; } export function resolveE2EUserKey(input: string | null | undefined) { if (!input) return null; const trimmed = input.trim(); if (!trimmed) return null; const roleMatch = (e2eUserIds as Record)[trimmed]; if (roleMatch) return roleMatch; if (e2eUsers[trimmed]) return trimmed; return null; } export function getE2ESession( input: string | null | undefined, ): SessionCookie | null { const userId = resolveE2EUserKey(input); if (!userId) return null; const user = e2eUsers[userId]; if (!user) return null; return { started_on: user.started_on || BASE_TIME, user_name: user.name || "E2E User", user_encrypted: false, user_id: user.id, role: user.user_type || "", }; } export function getE2EUserById(userId: string): User | null { return e2eUsers[userId] ? { ...e2eUsers[userId] } : null; } export function setE2EUser(user: User) { e2eUsers = { ...e2eUsers, [user.id]: { ...user } }; } export function updateE2EUser( userId: string, updates: Partial, ): User | null { const existing = e2eUsers[userId]; if (!existing) return null; const updated = { ...existing, ...updates, updated_on: BASE_TIME }; e2eUsers = { ...e2eUsers, [userId]: updated }; return { ...updated }; } export function getE2EAllProperties() { return [...e2eProperties]; } export function getE2EPropertiesByOwner(ownerId: string) { return e2eProperties.filter((property) => property.owner_id === ownerId); } export function getE2EPropertyById(propertyId: string): Property | null { return e2eProperties.find((property) => property.id === propertyId) || null; } export function addE2EProperty(ownerId: string, input: Partial) { const newProperty: Property = { id: crypto.randomUUID(), owner_id: ownerId, title: input.title || "New Property", description: input.description || "", address_1: input.address_1 || "", address_2: input.address_2 || "", city: input.city || "", state: input.state || "", zip: input.zip || "", country: input.country || "", bedrooms: input.bedrooms || 0, bathrooms: input.bathrooms || 0, property_type: input.property_type || "", monthly_rent: input.monthly_rent || 0, security_deposit: input.security_deposit || 0, lease_duration_months: input.lease_duration_months || 12, amenities: input.amenities || "", rental_status: "draft", created_on: BASE_TIME, updated_on: BASE_TIME, }; e2eProperties = [...e2eProperties, newProperty]; return newProperty; } export function addE2EPropertyMedia( propertyId: string, mediaType: string, fileUrl: string, fileName: string, displayOrder = 0, ) { const media: PropertyMedia = { id: crypto.randomUUID(), property_id: propertyId, media_type: mediaType, file_url: fileUrl, file_name: fileName, display_order: displayOrder, created_on: BASE_TIME, }; e2ePropertyMedia = [...e2ePropertyMedia, media]; return media; } function sortMediaByOrder(media: PropertyMedia[]) { return [...media].sort((a, b) => (a.display_order ?? 0) - (b.display_order ?? 0) ); } export function getE2EMediaByProperty(propertyId: string) { return sortMediaByOrder( e2ePropertyMedia.filter((media) => media.property_id === propertyId), ); } export function getE2EMediaByPropertyAndType( propertyId: string, mediaType: string, ) { return sortMediaByOrder( e2ePropertyMedia.filter((media) => media.property_id === propertyId && media.media_type === mediaType ), ); } export function updateE2EPropertyMediaOrder(id: string, displayOrder: number) { let updated: PropertyMedia | null = null; e2ePropertyMedia = e2ePropertyMedia.map((media) => { if (media.id !== id) return media; updated = { ...media, display_order: displayOrder }; return updated; }); return updated ? [updated] : []; } export function updateE2EMultipleMediaOrders( updates: { id: string; display_order: number }[], ) { const updatesMap = new Map( updates.map((update) => [update.id, update.display_order]), ); const changed: PropertyMedia[] = []; e2ePropertyMedia = e2ePropertyMedia.map((media) => { const nextOrder = updatesMap.get(media.id); if (nextOrder === undefined) return media; const updated = { ...media, display_order: nextOrder }; changed.push(updated); return updated; }); return changed; } export function deleteE2EPropertyMedia(id: string) { const existing = e2ePropertyMedia.find((media) => media.id === id) || null; e2ePropertyMedia = e2ePropertyMedia.filter((media) => media.id !== id); return existing ? [existing] : []; } export function deleteE2EAllMediaForProperty(propertyId: string) { const removed = e2ePropertyMedia.filter((media) => media.property_id === propertyId ); e2ePropertyMedia = e2ePropertyMedia.filter((media) => media.property_id !== propertyId ); return removed; } export function getE2EMediaCountByProperty(propertyId: string) { const media = getE2EMediaByProperty(propertyId); return { total: media.length, photos: media.filter((item) => item.media_type === "photo").length, videos: media.filter((item) => item.media_type === "video").length, documents: media.filter((item) => item.media_type === "document").length, }; } export function getE2EPrimaryPhoto(propertyId: string) { const photos = getE2EMediaByPropertyAndType(propertyId, "photo"); return photos[0] || null; } export function mergeE2EProperty( canonicalPropertyId: string, duplicatePropertyId: string, canonicalAddress: string, ): PropertyMergeResult { if (canonicalPropertyId === duplicatePropertyId) { throw new Error("Select two different properties to merge."); } const canonical = getE2EPropertyById(canonicalPropertyId); const duplicate = getE2EPropertyById(duplicatePropertyId); if (!canonical || !duplicate) { throw new Error("Property not found."); } const warnings: string[] = []; const canonicalActive = e2eLeases.some((lease) => lease.property_id === canonicalPropertyId && lease.lease_status === "active" ); const duplicateActive = e2eLeases.some((lease) => lease.property_id === duplicatePropertyId && lease.lease_status === "active" ); if (canonicalActive && duplicateActive) { warnings.push( "Both properties have active leases. Confirm lease expectations after merge.", ); } const canonicalSchedules = e2eRentSchedules.filter((schedule) => schedule.property_id === canonicalPropertyId ).length; const duplicateSchedules = e2eRentSchedules.filter((schedule) => schedule.property_id === duplicatePropertyId ).length; if (canonicalSchedules && duplicateSchedules) { warnings.push( "Both properties have rent schedules. Review schedules after merge.", ); } let leaseUpdates = 0; let scheduleUpdates = 0; let applicationUpdates = 0; let maintenanceUpdates = 0; let mediaUpdates = 0; e2eLeases = e2eLeases.map((lease) => { if (lease.property_id !== duplicatePropertyId) return lease; leaseUpdates += 1; return { ...lease, property_id: canonicalPropertyId, property_address: canonicalAddress || lease.property_address || "", updated_on: BASE_TIME, }; }); e2eRentSchedules = e2eRentSchedules.map((schedule) => { if (schedule.property_id !== duplicatePropertyId) return schedule; scheduleUpdates += 1; return { ...schedule, property_id: canonicalPropertyId, updated_on: BASE_TIME, }; }); e2eTenantApplications = e2eTenantApplications.map((application) => { if (application.property_id !== duplicatePropertyId) return application; applicationUpdates += 1; return { ...application, property_id: canonicalPropertyId }; }); e2eMaintenanceRequests = e2eMaintenanceRequests.map((request) => { if (request.property_id !== duplicatePropertyId) return request; maintenanceUpdates += 1; return { ...request, property_id: canonicalPropertyId }; }); e2ePropertyMedia = e2ePropertyMedia.map((media) => { if (media.property_id !== duplicatePropertyId) return media; mediaUpdates += 1; return { ...media, property_id: canonicalPropertyId }; }); e2eProperties = e2eProperties .map((property) => property.id === canonicalPropertyId ? { ...property, updated_on: BASE_TIME } : property ) .filter((property) => property.id !== duplicatePropertyId); return { canonicalPropertyId, duplicatePropertyId, updated: { leases: leaseUpdates, rentSchedules: scheduleUpdates, tenantApplications: applicationUpdates, maintenanceRequests: maintenanceUpdates, propertyMedia: mediaUpdates, }, warnings, }; } export function getE2ELeasesByTenant(tenantId: string) { return e2eLeases.filter((lease) => lease.tenant_id === tenantId); } export function getE2ELeasesByOwner(ownerId: string) { return e2eLeases.filter((lease) => lease.owner_id === ownerId); } export function getE2ELeaseById(leaseId: string): Lease | null { return e2eLeases.find((lease) => lease.id === leaseId) || null; } export function getE2EMessagesByUser(userId: string) { return e2eMessages.filter((message) => message.sender_id === userId || message.recipient_id === userId ); } export function markE2EMessageAsRead(messageId: string) { e2eMessages = e2eMessages.map((message) => message.id === messageId ? { ...message, is_read: true } : message ); return true; } export function deleteE2EMessage(messageId: string) { const existing = e2eMessages.find((message) => message.id === messageId) || null; e2eMessages = e2eMessages.filter((message) => message.id !== messageId); return existing; } export function deleteE2EMessagesForUser(userId: string, messageIds: string[]) { if (messageIds.length === 0) return []; const idSet = new Set(messageIds); const removed: Message[] = []; e2eMessages = e2eMessages.filter((message) => { const isOwned = message.sender_id === userId || message.recipient_id === userId; const shouldDelete = isOwned && idSet.has(message.id); if (shouldDelete) removed.push(message); return !shouldDelete; }); return removed; } export function getE2EMaintenanceRequestsByUser(userId: string) { return e2eMaintenanceRequests.filter((request) => request.tenant_id === userId ); } export function getE2EMaintenanceRequestsByLandlord(landlordId: string) { const propertyIds = e2eProperties .filter((property) => property.owner_id === landlordId) .map((property) => property.id); return e2eMaintenanceRequests.filter((request) => propertyIds.includes(request.property_id || "") ); } export function getE2EMaintenanceDataByLandlord( landlordId: string, ): MaintenanceData[] { return getE2EMaintenanceRequestsByLandlord(landlordId).map((request) => { const tenant = request.tenant_id ? e2eUsers[request.tenant_id] : null; const property = request.property_id ? e2eProperties.find((item) => item.id === request.property_id) : null; return { id: request.id, property_id: request.property_id || "", tenant_id: request.tenant_id || "", request_type: request.request_type || null, description: request.description || null, status: request.status || null, priority: request.priority || null, created_on: request.created_on || BASE_TIME, resolved_on: request.resolved_on || null, tenant_name: tenant?.name || "Tenant", tenant_email: tenant?.email || "", tenant_phone: tenant?.phone || "", property_address: property?.address_1 || "", property_city: property?.city || "", property_state: property?.state || "", property_zip: property?.zip || "", property_title: property?.title || "Property", }; }); } export function updateE2EMaintenanceRequest( requestId: string, updates: { status?: string; priority?: string }, ) { e2eMaintenanceRequests = e2eMaintenanceRequests.map((request) => { if (request.id !== requestId) return request; return { ...request, status: updates.status ?? request.status, priority: updates.priority ?? request.priority, resolved_on: updates.status === "completed" ? BASE_TIME : request.resolved_on, }; }); } export function createE2EMaintenanceRequest( propertyId: string, tenantId: string, requestType: string, description: string, priority: string, ) { const newRequest: MaintenanceRequest = { id: crypto.randomUUID(), property_id: propertyId, tenant_id: tenantId, request_type: requestType, description, status: "open", priority, created_on: BASE_TIME, resolved_on: null, }; e2eMaintenanceRequests = [newRequest, ...e2eMaintenanceRequests]; return newRequest; } export function getE2ETenantApplicationsByLandlord(landlordId: string) { const propertyIds = e2eProperties .filter((property) => property.owner_id === landlordId) .map((property) => property.id); return e2eTenantApplications .filter(( app, ): app is TenantApplication & { property_id: string } => typeof app.property_id === "string" && propertyIds.includes(app.property_id) ) .map((app) => { const applicantId = app.applicant_id ?? ""; const applicant = applicantId ? e2eUsers[applicantId] : null; const property = e2eProperties.find((item) => item.id === app.property_id ); return { ...app, applicant_name: applicant?.name || "Applicant", applicant_email: applicant?.email || "", property_address: property?.address_1 || "", monthly_income: 6800, application_notes: "Strong rental history.", status: app.application_status || "pending", created_at: app.submitted_on, document_urls_json: app.document_urls_json ? JSON.stringify(app.document_urls_json) : null, }; }); } export function hasE2EApplication(propertyId: string, applicantId: string) { return e2eTenantApplications.some((app) => app.property_id === propertyId && app.applicant_id === applicantId ); } export function createE2ETenantApplication( propertyId: string, applicantId: string, applicationData: string, documentUrlsJson: string | null = null, ) { const newApplication: TenantApplication = { id: crypto.randomUUID(), property_id: propertyId, applicant_id: applicantId, application_status: "pending", application_data_encrypted: applicationData, document_urls_json: documentUrlsJson ? JSON.parse(documentUrlsJson) as Record : null, submitted_on: BASE_TIME, reviewed_on: null, }; e2eTenantApplications = [newApplication, ...e2eTenantApplications]; return newApplication; } export function updateE2ETenantApplicationStatus( applicationId: string, status: "approved" | "rejected", ) { e2eTenantApplications = e2eTenantApplications.map((app) => app.id === applicationId ? { ...app, application_status: status, reviewed_on: BASE_TIME } : app ); } export function addE2ETenantInvite( landlordId: string, propertyId: string, options: { availableOn?: number | null } = {}, ) { const existing = e2eTenantInvites.find((item) => item.landlord_id === landlordId && item.property_id === propertyId ); if (existing) return { ...existing }; const now = BASE_TIME; const invite: TenantInvite = { id: crypto.randomUUID(), landlord_id: landlordId, property_id: propertyId, available_on: options.availableOn ?? null, invited_emails: JSON.stringify([]), created_on: now, updated_on: now, }; e2eTenantInvites = [invite, ...e2eTenantInvites]; return invite; } export function getE2ETwitterInviteById(inviteId: string) { const invite = e2eTenantInvites.find((item) => item.id === inviteId); return invite ? { ...invite } : null; } export function getE2ETwitterInviteByProperty( landlordId: string, propertyId: string, ) { const invite = e2eTenantInvites.find((item) => item.landlord_id === landlordId && item.property_id === propertyId ); return invite ? { ...invite } : null; } export function updateE2ETwitterInvite( inviteId: string, updates: Partial, ) { let updated: TenantInvite | null = null; e2eTenantInvites = e2eTenantInvites.map((invite) => { if (invite.id !== inviteId) return invite; updated = { ...invite, ...updates, updated_on: BASE_TIME }; return updated; }); return updated; } function parseInviteEmails(rawEmails?: string | null) { if (!rawEmails) return []; try { const parsed = JSON.parse(rawEmails); if (!Array.isArray(parsed)) return []; return parsed.filter((entry) => typeof entry === "string"); } catch { return []; } } export function getE2ETwitterInvitesByEmail(email: string) { const normalizedEmail = email.trim().toLowerCase(); if (!normalizedEmail) return []; return e2eTenantInvites.filter((invite) => { const emails = parseInviteEmails(invite.invited_emails); return emails.some((entry) => entry.toLowerCase() === normalizedEmail); }).map((invite) => ({ ...invite })); } export function getE2EPaymentMethodsByUser(userId: string) { return e2ePaymentMethods.filter((method) => method.user_id === userId); } export function getE2EPaymentTransactionsByLease(leaseId: string) { return e2ePaymentTransactions.filter((tx) => tx.lease_id === leaseId); } export function getE2EPaymentTransactionsByUser(userId: string) { const leaseIds = e2eLeases .filter((lease) => lease.tenant_id === userId) .map((lease) => lease.id); return e2ePaymentTransactions.filter((tx) => leaseIds.includes(tx.lease_id || "") ); } export function getE2EPaymentTransactionsByOwner(ownerId: string) { const leaseIds = e2eLeases .filter((lease) => lease.owner_id === ownerId) .map((lease) => lease.id); return e2ePaymentTransactions.filter((tx) => leaseIds.includes(tx.lease_id || "") ); } export function getE2EDocumentsByLease(leaseId: string) { return e2eLeaseDocuments.filter((doc) => doc.lease_id === leaseId); } export function getE2EDocumentById(documentId: string) { return e2eLeaseDocuments.find((doc) => doc.id === documentId) || null; } export function getE2EDocumentsByLeaseAndType( leaseId: string, documentType: string, ) { return e2eLeaseDocuments.filter((doc) => doc.lease_id === leaseId && doc.document_type === documentType ); } export function createE2ELeaseDocument( leaseId: string, documentType: string, fileUrl: string, fileName: string, ) { const newDocument: LeaseDocument = { id: crypto.randomUUID(), lease_id: leaseId, document_type: documentType, file_url: fileUrl, file_name: fileName, signed_by_landlord: false, signed_by_tenant: false, signature_date: null, created_on: BASE_TIME, }; e2eLeaseDocuments = [newDocument, ...e2eLeaseDocuments]; return newDocument; } export function deleteE2ELeaseDocument(documentId: string) { const existing = getE2EDocumentById(documentId); e2eLeaseDocuments = e2eLeaseDocuments.filter((doc) => doc.id !== documentId); return existing; } export function signE2EDocumentAsLandlord(documentId: string) { let updated: LeaseDocument | null = null; e2eLeaseDocuments = e2eLeaseDocuments.map((doc) => { if (doc.id !== documentId) return doc; updated = { ...doc, signed_by_landlord: true, signature_date: BASE_TIME, }; return updated; }); return updated; } export function signE2EDocumentAsTenant(documentId: string) { let updated: LeaseDocument | null = null; e2eLeaseDocuments = e2eLeaseDocuments.map((doc) => { if (doc.id !== documentId) return doc; updated = { ...doc, signed_by_tenant: true, signature_date: BASE_TIME, }; return updated; }); return updated; } export function updateE2EDocumentFile( documentId: string, fileUrl: string, fileName: string, ) { let updated: LeaseDocument | null = null; e2eLeaseDocuments = e2eLeaseDocuments.map((doc) => { if (doc.id !== documentId) return doc; updated = { ...doc, file_url: fileUrl, file_name: fileName, signed_by_landlord: false, signed_by_tenant: false, signature_date: null, }; return updated; }); return updated; } export function getE2ELandlordDashboardData(userId: string) { const properties = getE2EPropertiesByOwner(userId); const leases = getE2ELeasesByOwner(userId); const tenantApplications = getE2ETenantApplicationsByLandlord(userId); const maintenanceRequests = getE2EMaintenanceRequestsByLandlord(userId).map( (request) => ({ ...request, tenant_name: e2eUsers[request.tenant_id || ""]?.name || "Tenant", tenant_email: e2eUsers[request.tenant_id || ""]?.email || "", property_address: getE2EPropertyById(request.property_id || "")?.address_1 || "", created_at: request.created_on, }), ); const recentMessages = getE2EMessagesByUser(userId).map((message) => ({ ...message, sender_name: e2eUsers[message.sender_id || ""]?.name || "Tenant", created_at: message.created_on, })); const monthlyRevenue = properties.reduce( (sum, property) => sum + (property.monthly_rent || 0), 0, ); return { propertyData: { total: properties.length, available: properties.filter((property) => property.rental_status === "available") .length, occupied: properties.filter((property) => property.rental_status === "occupied") .length, draft: properties.filter((property) => property.rental_status === "draft") .length, }, leaseData: { active: leases.filter((lease) => lease.lease_status === "active").length, draft: leases.filter((lease) => lease.lease_status === "draft").length, expired: leases.filter((lease) => lease.lease_status === "expired").length, }, applicationData: { pending: tenantApplications.filter((app) => app.status === "pending") .length, total_this_month: tenantApplications.length, }, paymentData: { expected_this_month: leases .filter((lease) => lease.lease_status === "active") .reduce((sum, lease) => sum + (lease.rent || 0), 0), received_this_month: monthlyRevenue, overdue: 0, }, properties, tenantApplications, maintenanceRequests, recentMessages, monthlyRevenue, }; } export function getE2EPendingApprovals() { return e2eLeases.filter((lease) => lease.approval_status === "pending" || lease.approval_status === "requested" ); } export function addE2ELeaseWithApproval( propertyId: string, tenantId: string, ownerId: string, approvalStatus: string = "pending", ) { const newLease: Lease = { id: crypto.randomUUID(), property_id: propertyId, property_address: getE2EPropertyById(propertyId)?.address_1 || "", tenant_id: tenantId, owner_id: ownerId, start_date: BASE_TIME + 1000 * 60 * 60 * 24 * 7, end_date: BASE_TIME + 1000 * 60 * 60 * 24 * 377, rent: 2000, security_deposit: 2000, lease_status: "draft", approval_status: approvalStatus, approval_requested_at: approvalStatus === "pending" ? BASE_TIME : null, created_on: BASE_TIME, updated_on: BASE_TIME, }; e2eLeases = [newLease, ...e2eLeases]; return newLease; }