Multi-Tenant Architektur Beispiele

Umfassende Multi-Tenant Architektur Muster mit Datenisolation, Tenant-Management, Ressourcenquoten und White-Label Lösungen

💻 Datenisolationsstrategien

🔴 complex ⭐⭐⭐⭐

Implementierung verschiedener Datenisolationsmuster für Multi-Tenant Anwendungen

⏱️ 45 min 🏷️ data-isolation, database-strategies, multi-tenant-architecture
Prerequisites: Database design knowledge, Multi-tenancy concepts

// Multi-Tenant Data Isolation Patterns

// Tenant Context
interface TenantContext {
  tenantId: string
  tenantName: string
  domain: string
  plan: 'basic' | 'premium' | 'enterprise'
  settings: TenantSettings
  createdAt: Date
}

interface TenantSettings {
  timezone: string
  currency: string
  locale: string
  features: Record<string, boolean>
  limits: ResourceLimits
}

interface ResourceLimits {
  maxUsers: number
  maxStorage: number // in GB
  maxApiCalls: number
  maxProjects: number
}

// Strategy 1: Database per Tenant (Complete Isolation)
class DatabasePerTenantStrategy {
  private connections: Map<string, any> = new Map()

  constructor(private connectionStringTemplate: string) {
    // Template: "postgresql://user:password@localhost/tenant_{tenant_id}_db"
  }

  async getConnection(tenantId: string): Promise<any> {
    let connection = this.connections.get(tenantId)

    if (!connection) {
      const connectionString = this.connectionStringTemplate.replace('{tenant_id}', tenantId)
      connection = await this.createConnection(connectionString)
      this.connections.set(tenantId, connection)
    }

    return connection
  }

  private async createConnection(connectionString: string): Promise<any> {
    // Simulated database connection
    console.log(`Creating connection to: ${connectionString}`)
    return {
      query: async (sql: string, params?: any[]) => {
        console.log(`[${this.extractTenantId(connectionString)}] Executing: ${sql}`, params || [])
        return { rows: [], rowCount: 0 }
      },
      close: async () => {
        console.log(`Closing connection: ${connectionString}`)
      }
    }
  }

  private extractTenantId(connectionString: string): string {
    const match = connectionString.match(/tenant_(.+?)_db/)
    return match ? match[1] : 'unknown'
  }

  async createTenantDatabase(tenantId: string, schema: string): Promise<void> {
    console.log(`Creating database schema for tenant ${tenantId}`)
    // In real implementation, would execute SQL CREATE DATABASE and schema setup
  }

  async dropTenantDatabase(tenantId: string): Promise<void> {
    console.log(`Dropping database for tenant ${tenantId}`)
    const connection = this.connections.get(tenantId)
    if (connection) {
      await connection.close()
      this.connections.delete(tenantId)
    }
  }
}

// Strategy 2: Schema per Tenant (Shared Database)
class SchemaPerTenantStrategy {
  private connection: any
  private tenantSchemas: Set<string> = new Set()

  constructor(private connectionString: string) {
    this.initializeConnection()
  }

  private async initializeConnection(): Promise<void> {
    this.connection = {
      query: async (sql: string, params?: any[]) => {
        console.log(`Executing: ${sql}`, params || [])
        return { rows: [], rowCount: 0 }
      }
    }
  }

  async getConnection(tenantId: string): Promise<any> {
    const schemaName = `tenant_${tenantId}`

    if (!this.tenantSchemas.has(tenantId)) {
      await this.createTenantSchema(tenantId)
    }

    return {
      query: async (sql: string, params?: any[]) => {
        const schemaSql = sql.replace(/FROM\s+(\w+)/gi, `FROM ${schemaName}.$1`)
        const schemaSql2 = schemaSql.replace(/INSERT\s+INTO\s+(\w+)/gi, `INSERT INTO ${schemaName}.$1`)
        const schemaSql3 = schemaSql2.replace(/UPDATE\s+(\w+)/gi, `UPDATE ${schemaName}.$1`)
        const finalSql = schemaSql3.replace(/DELETE\s+FROM\s+(\w+)/gi, `DELETE FROM ${schemaName}.$1`)

        console.log(`[${schemaName}] ${finalSql}`, params || [])
        return { rows: [], rowCount: 0 }
      }
    }
  }

  private async createTenantSchema(tenantId: string): Promise<void> {
    const schemaName = `tenant_${tenantId}`
    console.log(`Creating schema ${schemaName}`)

    // Simulate schema creation
    await this.connection.query(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`)

    // Create tables in schema
    const tables = [
      `CREATE TABLE IF NOT EXISTS ${schemaName}.users (id SERIAL PRIMARY KEY, name VARCHAR(255))`,
      `CREATE TABLE IF NOT EXISTS ${schemaName}.products (id SERIAL PRIMARY KEY, name VARCHAR(255), price DECIMAL)`
    ]

    for (const tableSql of tables) {
      await this.connection.query(tableSql)
    }

    this.tenantSchemas.add(tenantId)
  }

  async dropTenantSchema(tenantId: string): Promise<void> {
    const schemaName = `tenant_${tenantId}`
    console.log(`Dropping schema ${schemaName}`)
    await this.connection.query(`DROP SCHEMA IF EXISTS ${schemaName} CASCADE`)
    this.tenantSchemas.delete(tenantId)
  }
}

// Strategy 3: Shared Table with Tenant Column (Row-Level Security)
class SharedTableStrategy {
  private connection: any
  private tenantContext: TenantContext | null = null

  constructor(connectionString: string) {
    this.initializeConnection(connectionString)
  }

  private async initializeConnection(connectionString: string): Promise<void> {
    this.connection = {
      query: async (sql: string, params?: any[]) => {
        console.log(`[Shared] ${sql}`, params || [])
        return { rows: [], rowCount: 0 }
      }
    }

    // Enable row-level security
    await this.setupRowLevelSecurity()
  }

  setTenantContext(tenant: TenantContext): void {
    this.tenantContext = tenant
  }

  async query(sql: string, params?: any[]): Promise<any> {
    if (!this.tenantContext) {
      throw new Error('Tenant context not set')
    }

    // Add tenant filter to all queries
    let tenantAwareSql = sql

    // Add WHERE clause for tenant_id if not present
    if (!sql.toLowerCase().includes('tenant_id')) {
      if (sql.toLowerCase().includes('where')) {
        tenantAwareSql = sql.replace(/where/gi, `WHERE tenant_id = '${this.tenantContext.tenantId}' AND`)
      } else if (sql.toLowerCase().includes('from') && !sql.toLowerCase().includes('update') && !sql.toLowerCase().includes('delete')) {
        tenantAwareSql = sql.replace(/from\s+(\w+)/gi, `FROM $1 WHERE tenant_id = '${this.tenantContext.tenantId}'`)
      }
    }

    // Automatically add tenant_id to INSERT statements
    if (sql.toLowerCase().startsWith('insert')) {
      const hasTenantId = sql.toLowerCase().includes('tenant_id')
      if (!hasTenantId) {
        const valuesMatch = sql.match(/VALUES\s*\((.*?)\)/i)
        if (valuesMatch) {
          const existingValues = valuesMatch[1]
          const newValues = existingValues ? `${existingValues}, '${this.tenantContext.tenantId}'` : `'${this.tenantContext.tenantId}'`
          tenantAwareSql = sql.replace(/values\s*\((.*?)\)/i, `VALUES (${newValues})`)

          // Add tenant_id to column list
          tenantAwareSql = tenantAwareSql.replace(/insert\s+into\s+(\w+)\s*\((.*?)\)/i, (match, table, columns) => {
            if (columns) {
              return `INSERT INTO ${table} (${columns}, tenant_id)`
            }
            return match
          })
        }
      }
    }

    return await this.connection.query(tenantAwareSql, params)
  }

  private async setupRowLevelSecurity(): Promise<void> {
    console.log('Setting up row-level security policies')

    // Simulate RLS setup
    const policies = [
      'CREATE POLICY tenant_isolation ON users FOR ALL TO application_user USING (tenant_id = current_setting('app.current_tenant_id')::uuid)',
      'CREATE POLICY tenant_isolation ON products FOR ALL TO application_user USING (tenant_id = current_setting('app.current_tenant_id')::uuid)'
    ]

    for (const policy of policies) {
      await this.connection.query(policy)
    }
  }

  async createTenant(tenant: TenantContext): Promise<void> {
    // Create tenant-specific views or triggers if needed
    console.log(`Setting up tenant ${tenant.tenantId} in shared database`)
  }

  async removeTenant(tenantId: string): Promise<void> {
    // Soft delete or archive tenant data
    await this.connection.query(
      `UPDATE users SET deleted_at = NOW() WHERE tenant_id = $1`,
      [tenantId]
    )
  }
}

// Strategy 4: Hybrid Approach (Based on tenant plan)
class HybridDataIsolationStrategy {
  private databaseStrategy: DatabasePerTenantStrategy
  private schemaStrategy: SchemaPerTenantStrategy
  private sharedStrategy: SharedTableStrategy

  constructor() {
    this.databaseStrategy = new DatabasePerTenantStrategy('postgresql://user:password@localhost/tenant_{tenant_id}_db')
    this.schemaStrategy = new SchemaPerTenantStrategy('postgresql://user:password@localhost/multi_tenant_db')
    this.sharedStrategy = new SharedTableStrategy('postgresql://user:password@localhost/multi_tenant_db')
  }

  async getConnection(tenant: TenantContext): Promise<any> {
    switch (tenant.plan) {
      case 'enterprise':
        return await this.databaseStrategy.getConnection(tenant.tenantId)

      case 'premium':
        return await this.schemaStrategy.getConnection(tenant.tenantId)

      case 'basic':
        this.sharedStrategy.setTenantContext(tenant)
        return this.sharedStrategy

      default:
        throw new Error(`Unknown tenant plan: ${tenant.plan}`)
    }
  }

  async createTenant(tenant: TenantContext): Promise<void> {
    switch (tenant.plan) {
      case 'enterprise':
        await this.databaseStrategy.createTenantDatabase(tenant.tenantId, 'enterprise_schema')
        break

      case 'premium':
        await this.schemaStrategy.createTenantSchema(tenant.tenantId)
        break

      case 'basic':
        await this.sharedStrategy.createTenant(tenant)
        break
    }
  }

  async removeTenant(tenantId: string, plan: string): Promise<void> {
    switch (plan) {
      case 'enterprise':
        await this.databaseStrategy.dropTenantDatabase(tenantId)
        break

      case 'premium':
        await this.schemaStrategy.dropTenantSchema(tenantId)
        break

      case 'basic':
        await this.sharedStrategy.removeTenant(tenantId)
        break
    }
  }

  async getTenantDataUsage(tenant: TenantContext): Promise<{
    storageUsed: number
    recordCount: number
    databaseSize: number
  }> {
    const connection = await this.getConnection(tenant)

    // Simulate data usage query
    const usage = {
      storageUsed: Math.floor(Math.random() * 100), // GB
      recordCount: Math.floor(Math.random() * 1000000),
      databaseSize: Math.floor(Math.random() * 1000) // MB
    }

    console.log(`Tenant ${tenant.tenantId} usage:`, usage)
    return usage
  }
}

// Data Migration between Strategies
class DataMigrationService {
  async migrateTenant(
    fromStrategy: string,
    toStrategy: string,
    tenant: TenantContext
  ): Promise<{
    success: boolean
    recordsMigrated: number
    duration: number
    errors: string[]
  }> {
    const startTime = Date.now()
    const errors: string[] = []
    let recordsMigrated = 0

    console.log(`Starting migration for tenant ${tenant.tenantId} from ${fromStrategy} to ${toStrategy}`)

    try {
      // Simulate migration process
      const tables = ['users', 'products', 'orders']

      for (const table of tables) {
        console.log(`Migrating table: ${table}`)

        // Simulate data extraction and loading
        const recordCount = Math.floor(Math.random() * 10000)
        recordsMigrated += recordCount

        // Simulate processing time
        await new Promise(resolve => setTimeout(resolve, 100))
      }

      console.log(`Migration completed successfully. Records migrated: ${recordsMigrated}`)
    } catch (error) {
      errors.push(error instanceof Error ? error.message : 'Unknown error')
      console.error(`Migration failed:`, errors)
    }

    const duration = Date.now() - startTime

    return {
      success: errors.length === 0,
      recordsMigrated,
      duration,
      errors
    }
  }

  async validateMigration(
    sourceTenant: TenantContext,
    targetTenant: TenantContext
  ): Promise<{
    isValid: boolean
    discrepancies: Array<{
      table: string
      sourceCount: number
      targetCount: number
      difference: number
    }>
  }> {
    console.log(`Validating migration from ${sourceTenant.tenantId} to ${targetTenant.tenantId}`)

    // Simulate validation
    const tables = ['users', 'products', 'orders']
    const discrepancies = []

    for (const table of tables) {
      const sourceCount = Math.floor(Math.random() * 10000)
      const targetCount = sourceCount + Math.floor(Math.random() * 10) - 5 // Small random difference
      const difference = Math.abs(sourceCount - targetCount)

      if (difference > 0) {
        discrepancies.push({
          table,
          sourceCount,
          targetCount,
          difference
        })
      }
    }

    return {
      isValid: discrepancies.length === 0,
      discrepancies
    }
  }
}

// Performance Monitoring for Multi-Tenant Systems
class MultiTenantPerformanceMonitor {
  private queryMetrics: Map<string, Array<{ duration: number; timestamp: Date }>> = new Map()
  private connectionMetrics: Map<string, { active: number; total: number; errors: number }> = new Map()

  recordQuery(tenantId: string, duration: number): void {
    if (!this.queryMetrics.has(tenantId)) {
      this.queryMetrics.set(tenantId, [])
    }

    const metrics = this.queryMetrics.get(tenantId)!
    metrics.push({ duration, timestamp: new Date() })

    // Keep only last 1000 queries per tenant
    if (metrics.length > 1000) {
      metrics.shift()
    }
  }

  recordConnection(tenantId: string, isActive: boolean, isError: boolean = false): void {
    if (!this.connectionMetrics.has(tenantId)) {
      this.connectionMetrics.set(tenantId, { active: 0, total: 0, errors: 0 })
    }

    const metrics = this.connectionMetrics.get(tenantId)!

    if (isActive) {
      metrics.active++
    } else {
      metrics.active = Math.max(0, metrics.active - 1)
    }

    metrics.total++
    if (isError) {
      metrics.errors++
    }
  }

  getTenantMetrics(tenantId: string): {
    avgQueryTime: number
    totalQueries: number
    activeConnections: number
    errorRate: number
  } {
    const queryMetrics = this.queryMetrics.get(tenantId) || []
    const connectionMetrics = this.connectionMetrics.get(tenantId) || { active: 0, total: 0, errors: 0 }

    const avgQueryTime = queryMetrics.length > 0
      ? queryMetrics.reduce((sum, m) => sum + m.duration, 0) / queryMetrics.length
      : 0

    const errorRate = connectionMetrics.total > 0
      ? connectionMetrics.errors / connectionMetrics.total
      : 0

    return {
      avgQueryTime,
      totalQueries: queryMetrics.length,
      activeConnections: connectionMetrics.active,
      errorRate
    }
  }

  identifySlowTenants(thresholdMs: number = 1000): Array<{
    tenantId: string
    avgQueryTime: number
    totalQueries: number
  }> {
    const slowTenants: Array<{
      tenantId: string
      avgQueryTime: number
      totalQueries: number
    }> = []

    for (const [tenantId, queryMetrics] of this.queryMetrics.entries()) {
      if (queryMetrics.length === 0) continue

      const avgQueryTime = queryMetrics.reduce((sum, m) => sum + m.duration, 0) / queryMetrics.length

      if (avgQueryTime > thresholdMs) {
        slowTenants.push({
          tenantId,
          avgQueryTime,
          totalQueries: queryMetrics.length
        })
      }
    }

    return slowTenants.sort((a, b) => b.avgQueryTime - a.avgQueryTime)
  }
}

// Usage Examples
async function dataIsolationExample() {
  console.log('=== Multi-Tenant Data Isolation Strategies ===\n')

  // Create sample tenant contexts
  const enterpriseTenant: TenantContext = {
    tenantId: 'enterprise-123',
    tenantName: 'Acme Corporation',
    domain: 'acme.corp',
    plan: 'enterprise',
    settings: {
      timezone: 'UTC',
      currency: 'USD',
      locale: 'en-US',
      features: { 'advanced-analytics': true, 'custom-integrations': true },
      limits: { maxUsers: 1000, maxStorage: 1000, maxApiCalls: 10000000, maxProjects: 100 }
    },
    createdAt: new Date()
  }

  const premiumTenant: TenantContext = {
    tenantId: 'premium-456',
    tenantName: 'Startup Inc',
    domain: 'startup.io',
    plan: 'premium',
    settings: {
      timezone: 'America/New_York',
      currency: 'USD',
      locale: 'en-US',
      features: { 'advanced-analytics': true, 'custom-integrations': false },
      limits: { maxUsers: 100, maxStorage: 100, maxApiCalls: 1000000, maxProjects: 20 }
    },
    createdAt: new Date()
  }

  const basicTenant: TenantContext = {
    tenantId: 'basic-789',
    tenantName: 'Small Business LLC',
    domain: 'smallbiz.com',
    plan: 'basic',
    settings: {
      timezone: 'America/Los_Angeles',
      currency: 'USD',
      locale: 'en-US',
      features: { 'advanced-analytics': false, 'custom-integrations': false },
      limits: { maxUsers: 10, maxStorage: 10, maxApiCalls: 100000, maxProjects: 5 }
    },
    createdAt: new Date()
  }

  // Test different strategies
  console.log('1. Database per Tenant Strategy')
  const dbStrategy = new DatabasePerTenantStrategy('postgresql://user:password@localhost/tenant_{tenant_id}_db')
  await dbStrategy.getConnection(enterpriseTenant.tenantId)

  console.log('\n2. Schema per Tenant Strategy')
  const schemaStrategy = new SchemaPerTenantStrategy('postgresql://user:password@localhost/multi_tenant_db')
  await schemaStrategy.getConnection(premiumTenant.tenantId)

  console.log('\n3. Shared Table Strategy')
  const sharedStrategy = new SharedTableStrategy('postgresql://user:password@localhost/multi_tenant_db')
  sharedStrategy.setTenantContext(basicTenant)
  await sharedStrategy.query('SELECT * FROM users')

  console.log('\n4. Hybrid Strategy')
  const hybridStrategy = new HybridDataIsolationStrategy()

  // Create tenants with different plans
  await hybridStrategy.createTenant(enterpriseTenant)
  await hybridStrategy.createTenant(premiumTenant)
  await hybridStrategy.createTenant(basicTenant)

  // Get connections for different tenants
  await hybridStrategy.getConnection(enterpriseTenant)
  await hybridStrategy.getConnection(premiumTenant)
  await hybridStrategy.getConnection(basicTenant)

  // Monitor data usage
  console.log('\n5. Data Usage Monitoring')
  for (const tenant of [enterpriseTenant, premiumTenant, basicTenant]) {
    const usage = await hybridStrategy.getTenantDataUsage(tenant)
    console.log(`Tenant ${tenant.tenantId} (${tenant.plan}):`, usage)
  }

  // Performance monitoring
  console.log('\n6. Performance Monitoring')
  const monitor = new MultiTenantPerformanceMonitor()

  // Simulate some queries
  for (let i = 0; i < 10; i++) {
    monitor.recordQuery(enterpriseTenant.tenantId, Math.random() * 200 + 50)
    monitor.recordQuery(premiumTenant.tenantId, Math.random() * 300 + 100)
    monitor.recordQuery(basicTenant.tenantId, Math.random() * 500 + 200)
  }

  // Get metrics
  for (const tenantId of [enterpriseTenant.tenantId, premiumTenant.tenantId, basicTenant.tenantId]) {
    const metrics = monitor.getTenantMetrics(tenantId)
    console.log(`Tenant ${tenantId} metrics:`, metrics)
  }

  // Identify slow tenants
  const slowTenants = monitor.identifySlowTenants(400)
  if (slowTenants.length > 0) {
    console.log('\nSlow tenants identified:', slowTenants)
  }
}

export {
  TenantContext,
  TenantSettings,
  ResourceLimits,
  DatabasePerTenantStrategy,
  SchemaPerTenantStrategy,
  SharedTableStrategy,
  HybridDataIsolationStrategy,
  DataMigrationService,
  MultiTenantPerformanceMonitor,
  dataIsolationExample
}
        

💻 Tenant-Management

🔴 complex ⭐⭐⭐⭐

Umfassendes Tenant-Lebenszyklus-Management mit Onboarding, Konfiguration und Monitoring

⏱️ 60 min 🏷️ tenant-management, lifecycle, upgrades-downgrades
Prerequisites: Understanding of SaaS architecture, User management concepts

// Tenant Management System

// Tenant Types and Plans
enum TenantPlan {
  BASIC = 'basic',
  PREMIUM = 'premium',
  ENTERPRISE = 'enterprise'
}

enum TenantStatus {
  PENDING = 'pending',
  ACTIVE = 'active',
  SUSPENDED = 'suspended',
  TERMINATED = 'terminated'
}

interface Tenant {
  id: string
  name: string
  domain: string
  plan: TenantPlan
  status: TenantStatus
  settings: TenantSettings
  billing: BillingInfo
  createdAt: Date
  updatedAt: Date
  activatedAt?: Date
  suspendedAt?: Date
  terminatedAt?: Date
}

interface TenantSettings {
  timezone: string
  currency: string
  locale: string
  theme: {
    primaryColor: string
    logo: string
    customCSS?: string
  }
  features: Record<string, boolean>
  limits: ResourceLimits
  notifications: NotificationSettings
}

interface BillingInfo {
  billingEmail: string
  paymentMethod: string
  nextBillingDate: Date
  subscriptionAmount: number
  currency: string
  isActive: boolean
}

interface ResourceLimits {
  maxUsers: number
  maxStorage: number // GB
  maxApiCalls: number
  maxProjects: number
  maxIntegrations: number
  bandwidthLimit: number // GB/month
}

interface NotificationSettings {
  email: boolean
  sms: boolean
  inApp: boolean
  alerts: {
    diskUsage: boolean
    apiLimit: boolean
    userLimit: boolean
    billing: boolean
  }
}

// Tenant Repository
interface ITenantRepository {
  create(tenant: Omit<Tenant, 'id' | 'createdAt' | 'updatedAt'>): Promise<Tenant>
  findById(id: string): Promise<Tenant | null>
  findByDomain(domain: string): Promise<Tenant | null>
  update(id: string, updates: Partial<Tenant>): Promise<Tenant>
  delete(id: string): Promise<void>
  findAll(filters?: TenantFilters): Promise<Tenant[]>
  findActiveTenants(): Promise<Tenant[]>
}

interface TenantFilters {
  plan?: TenantPlan
  status?: TenantStatus
  createdAfter?: Date
  createdBefore?: Date
  limit?: number
  offset?: number
}

class InMemoryTenantRepository implements ITenantRepository {
  private tenants: Map<string, Tenant> = new Map()

  async create(tenantData: Omit<Tenant, 'id' | 'createdAt' | 'updatedAt'>): Promise<Tenant> {
    const tenant: Tenant = {
      ...tenantData,
      id: crypto.randomUUID(),
      createdAt: new Date(),
      updatedAt: new Date()
    }

    this.tenants.set(tenant.id, tenant)
    return tenant
  }

  async findById(id: string): Promise<Tenant | null> {
    return this.tenants.get(id) || null
  }

  async findByDomain(domain: string): Promise<Tenant | null> {
    for (const tenant of this.tenants.values()) {
      if (tenant.domain === domain) {
        return tenant
      }
    }
    return null
  }

  async update(id: string, updates: Partial<Tenant>): Promise<Tenant> {
    const tenant = this.tenants.get(id)
    if (!tenant) {
      throw new Error(`Tenant not found: ${id}`)
    }

    const updatedTenant = {
      ...tenant,
      ...updates,
      updatedAt: new Date()
    }

    this.tenants.set(id, updatedTenant)
    return updatedTenant
  }

  async delete(id: string): Promise<void> {
    if (!this.tenants.has(id)) {
      throw new Error(`Tenant not found: ${id}`)
    }
    this.tenants.delete(id)
  }

  async findAll(filters?: TenantFilters): Promise<Tenant[]> {
    let tenants = Array.from(this.tenants.values())

    if (filters) {
      if (filters.plan) {
        tenants = tenants.filter(t => t.plan === filters.plan)
      }
      if (filters.status) {
        tenants = tenants.filter(t => t.status === filters.status)
      }
      if (filters.createdAfter) {
        tenants = tenants.filter(t => t.createdAt >= filters.createdAfter!)
      }
      if (filters.createdBefore) {
        tenants = tenants.filter(t => t.createdAt <= filters.createdBefore!)
      }
    }

    return tenants.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
  }

  async findActiveTenants(): Promise<Tenant[]> {
    return Array.from(this.tenants.values()).filter(t => t.status === TenantStatus.ACTIVE)
  }
}

// Tenant Onboarding Service
class TenantOnboardingService {
  constructor(
    private tenantRepository: ITenantRepository,
    private notificationService: NotificationService,
    private provisioningService: TenantProvisioningService
  ) {}

  async onboardTenant(request: TenantOnboardingRequest): Promise<{
    tenant: Tenant
    onboardingSteps: OnboardingStep[]
  }> {
    console.log(`Starting onboarding for: ${request.name}`)

    const onboardingSteps: OnboardingStep[] = []

    // Step 1: Validate request
    onboardingSteps.push(await this.validateOnboardingRequest(request))

    // Step 2: Create tenant
    const tenant = await this.createTenantFromRequest(request)
    onboardingSteps.push({
      step: 'CREATE_TENANT',
      status: 'COMPLETED',
      message: 'Tenant record created',
      timestamp: new Date()
    })

    // Step 3: Provision resources
    onboardingSteps.push(await this.provisioningService.provisionTenantResources(tenant))

    // Step 4: Configure initial settings
    onboardingSteps.push(await this.configureTenantSettings(tenant))

    // Step 5: Send welcome email
    onboardingSteps.push(await this.sendWelcomeNotification(tenant))

    // Step 6: Activate tenant
    const activatedTenant = await this.activateTenant(tenant.id)
    onboardingSteps.push({
      step: 'ACTIVATE_TENANT',
      status: 'COMPLETED',
      message: 'Tenant activated successfully',
      timestamp: new Date()
    })

    return {
      tenant: activatedTenant,
      onboardingSteps
    }
  }

  private async validateOnboardingRequest(request: TenantOnboardingRequest): Promise<OnboardingStep> {
    // Validate domain uniqueness
    const existingTenant = await this.tenantRepository.findByDomain(request.domain)
    if (existingTenant) {
      throw new Error(`Domain ${request.domain} is already taken`)
    }

    // Validate email format
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    if (!emailRegex.test(request.adminEmail)) {
      throw new Error('Invalid admin email format')
    }

    // Validate plan
    if (!Object.values(TenantPlan).includes(request.plan as TenantPlan)) {
      throw new Error(`Invalid plan: ${request.plan}`)
    }

    return {
      step: 'VALIDATE_REQUEST',
      status: 'COMPLETED',
      message: 'Request validation passed',
      timestamp: new Date()
    }
  }

  private async createTenantFromRequest(request: TenantOnboardingRequest): Promise<Tenant> {
    const defaultSettings = this.getDefaultTenantSettings(request.plan as TenantPlan)
    const defaultLimits = this.getDefaultResourceLimits(request.plan as TenantPlan)

    const tenant = await this.tenantRepository.create({
      name: request.name,
      domain: request.domain,
      plan: request.plan as TenantPlan,
      status: TenantStatus.PENDING,
      settings: {
        ...defaultSettings,
        limits: defaultLimits
      },
      billing: {
        billingEmail: request.adminEmail,
        paymentMethod: 'CREDIT_CARD',
        nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
        subscriptionAmount: this.getPlanPricing(request.plan as TenantPlan),
        currency: 'USD',
        isActive: false
      }
    })

    return tenant
  }

  private async configureTenantSettings(tenant: Tenant): Promise<OnboardingStep> {
    // Configure default features based on plan
    const features = this.getDefaultFeatures(tenant.plan)

    await this.tenantRepository.update(tenant.id, {
      settings: {
        ...tenant.settings,
        features
      }
    })

    return {
      step: 'CONFIGURE_SETTINGS',
      status: 'COMPLETED',
      message: 'Default settings configured',
      timestamp: new Date()
    }
  }

  private async sendWelcomeNotification(tenant: Tenant): Promise<OnboardingStep> {
    try {
      await this.notificationService.sendWelcomeEmail(tenant)
      return {
        step: 'SEND_WELCOME_EMAIL',
        status: 'COMPLETED',
        message: 'Welcome email sent',
        timestamp: new Date()
      }
    } catch (error) {
      return {
        step: 'SEND_WELCOME_EMAIL',
        status: 'FAILED',
        message: error instanceof Error ? error.message : 'Failed to send welcome email',
        timestamp: new Date()
      }
    }
  }

  private async activateTenant(tenantId: string): Promise<Tenant> {
    return await this.tenantRepository.update(tenantId, {
      status: TenantStatus.ACTIVE,
      activatedAt: new Date(),
      billing: {
        // This should merge with existing billing info
        billingEmail: '', // Will be set in update
        paymentMethod: '',
        nextBillingDate: new Date(),
        subscriptionAmount: 0,
        currency: 'USD',
        isActive: true
      }
    })
  }

  private getDefaultTenantSettings(plan: TenantPlan): Omit<TenantSettings, 'limits'> {
    const baseSettings = {
      timezone: 'UTC',
      currency: 'USD',
      locale: 'en-US',
      theme: {
        primaryColor: '#007bff',
        logo: '/default-logo.png'
      },
      notifications: {
        email: true,
        sms: false,
        inApp: true,
        alerts: {
          diskUsage: true,
          apiLimit: true,
          userLimit: true,
          billing: true
        }
      }
    }

    return baseSettings
  }

  private getDefaultResourceLimits(plan: TenantPlan): ResourceLimits {
    switch (plan) {
      case TenantPlan.BASIC:
        return {
          maxUsers: 5,
          maxStorage: 10,
          maxApiCalls: 10000,
          maxProjects: 3,
          maxIntegrations: 2,
          bandwidthLimit: 50
        }

      case TenantPlan.PREMIUM:
        return {
          maxUsers: 50,
          maxStorage: 100,
          maxApiCalls: 1000000,
          maxProjects: 20,
          maxIntegrations: 10,
          bandwidthLimit: 500
        }

      case TenantPlan.ENTERPRISE:
        return {
          maxUsers: 1000,
          maxStorage: 1000,
          maxApiCalls: 10000000,
          maxProjects: 100,
          maxIntegrations: 50,
          bandwidthLimit: 5000
        }

      default:
        throw new Error(`Unknown plan: ${plan}`)
    }
  }

  private getDefaultFeatures(plan: TenantPlan): Record<string, boolean> {
    const baseFeatures = {
      'dashboard': true,
      'user-management': true,
      'basic-analytics': true
    }

    switch (plan) {
      case TenantPlan.BASIC:
        return baseFeatures

      case TenantPlan.PREMIUM:
        return {
          ...baseFeatures,
          'advanced-analytics': true,
          'api-access': true,
          'team-collaboration': true,
          'export-data': true
        }

      case TenantPlan.ENTERPRISE:
        return {
          ...baseFeatures,
          'advanced-analytics': true,
          'api-access': true,
          'team-collaboration': true,
          'export-data': true,
          'custom-integrations': true,
          'single-sign-on': true,
          'white-label': true,
          'dedicated-support': true,
          'audit-logs': true,
          'custom-domains': true
        }

      default:
        return baseFeatures
    }
  }

  private getPlanPricing(plan: TenantPlan): number {
    switch (plan) {
      case TenantPlan.BASIC: return 29
      case TenantPlan.PREMIUM: return 99
      case TenantPlan.ENTERPRISE: return 499
      default: return 0
    }
  }
}

// Tenant Lifecycle Management
class TenantLifecycleManager {
  constructor(
    private tenantRepository: ITenantRepository,
    private monitoringService: TenantMonitoringService,
    private notificationService: NotificationService
  ) {}

  async upgradeTenantPlan(tenantId: string, newPlan: TenantPlan): Promise<Tenant> {
    const tenant = await this.tenantRepository.findById(tenantId)
    if (!tenant) {
      throw new Error(`Tenant not found: ${tenantId}`)
    }

    if (tenant.plan === newPlan) {
      throw new Error(`Tenant already on plan: ${newPlan}`)
    }

    console.log(`Upgrading tenant ${tenantId} from ${tenant.plan} to ${newPlan}`)

    // Update resource limits
    const newLimits = this.getResourceLimitsForPlan(newPlan)
    const newFeatures = this.getFeaturesForPlan(newPlan)

    const updatedTenant = await this.tenantRepository.update(tenantId, {
      plan: newPlan,
      settings: {
        ...tenant.settings,
        limits: newLimits,
        features: newFeatures
      },
      billing: {
        ...tenant.billing,
        subscriptionAmount: this.getPlanPrice(newPlan)
      }
    })

    // Send notification
    await this.notificationService.sendPlanUpgradeNotification(updatedTenant, tenant.plan)

    return updatedTenant
  }

  async suspendTenant(tenantId: string, reason: string): Promise<Tenant> {
    const tenant = await this.tenantRepository.findById(tenantId)
    if (!tenant) {
      throw new Error(`Tenant not found: ${tenantId}`)
    }

    if (tenant.status === TenantStatus.SUSPENDED) {
      throw new Error('Tenant is already suspended')
    }

    console.log(`Suspending tenant ${tenantId}. Reason: ${reason}`)

    const updatedTenant = await this.tenantRepository.update(tenantId, {
      status: TenantStatus.SUSPENDED,
      suspendedAt: new Date()
    })

    // Disable access
    await this.monitoringService.disableTenantAccess(tenantId)

    // Send notification
    await this.notificationService.sendSuspensionNotification(updatedTenant, reason)

    return updatedTenant
  }

  async reactivateTenant(tenantId: string): Promise<Tenant> {
    const tenant = await this.tenantRepository.findById(tenantId)
    if (!tenant) {
      throw new Error(`Tenant not found: ${tenantId}`)
    }

    if (tenant.status !== TenantStatus.SUSPENDED) {
      throw new Error('Only suspended tenants can be reactivated')
    }

    console.log(`Reactivating tenant ${tenantId}`)

    const updatedTenant = await this.tenantRepository.update(tenantId, {
      status: TenantStatus.ACTIVE,
      suspendedAt: undefined
    })

    // Enable access
    await this.monitoringService.enableTenantAccess(tenantId)

    // Send notification
    await this.notificationService.sendReactivationNotification(updatedTenant)

    return updatedTenant
  }

  async terminateTenant(tenantId: string, reason: string): Promise<void> {
    const tenant = await this.tenantRepository.findById(tenantId)
    if (!tenant) {
      throw new Error(`Tenant not found: ${tenantId}`)
    }

    console.log(`Terminating tenant ${tenantId}. Reason: ${reason}`)

    // Mark as terminated
    await this.tenantRepository.update(tenantId, {
      status: TenantStatus.TERMINATED,
      terminatedAt: new Date()
    })

    // Archive data
    await this.monitoringService.archiveTenantData(tenantId)

    // Disable access
    await this.monitoringService.disableTenantAccess(tenantId)

    // Send final notification
    await this.notificationService.sendTerminationNotification(tenant, reason)
  }

  private getResourceLimitsForPlan(plan: TenantPlan): ResourceLimits {
    // Implementation would get the limits for the specified plan
    // This is simplified for the example
    return {
      maxUsers: 100,
      maxStorage: 100,
      maxApiCalls: 1000000,
      maxProjects: 20,
      maxIntegrations: 10,
      bandwidthLimit: 500
    }
  }

  private getFeaturesForPlan(plan: TenantPlan): Record<string, boolean> {
    // Implementation would get the features for the specified plan
    return {
      'dashboard': true,
      'user-management': true,
      'basic-analytics': true
    }
  }

  private getPlanPrice(plan: TenantPlan): number {
    switch (plan) {
      case TenantPlan.BASIC: return 29
      case TenantPlan.PREMIUM: return 99
      case TenantPlan.ENTERPRISE: return 499
      default: return 0
    }
  }
}

// Supporting Interfaces and Classes

interface TenantOnboardingRequest {
  name: string
  domain: string
  adminEmail: string
  plan: string
  companySize?: string
  industry?: string
}

interface OnboardingStep {
  step: string
  status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED'
  message: string
  timestamp: Date
  error?: string
}

interface NotificationService {
  sendWelcomeEmail(tenant: Tenant): Promise<void>
  sendPlanUpgradeNotification(tenant: Tenant, oldPlan: TenantPlan): Promise<void>
  sendSuspensionNotification(tenant: Tenant, reason: string): Promise<void>
  sendReactivationNotification(tenant: Tenant): Promise<void>
  sendTerminationNotification(tenant: Tenant, reason: string): Promise<void>
}

interface TenantProvisioningService {
  provisionTenantResources(tenant: Tenant): Promise<OnboardingStep>
}

interface TenantMonitoringService {
  disableTenantAccess(tenantId: string): Promise<void>
  enableTenantAccess(tenantId: string): Promise<void>
  archiveTenantData(tenantId: string): Promise<void>
}

// Mock implementations for demo purposes
class MockNotificationService implements NotificationService {
  async sendWelcomeEmail(tenant: Tenant): Promise<void> {
    console.log(`Sending welcome email to ${tenant.billing.billingEmail}`)
  }

  async sendPlanUpgradeNotification(tenant: Tenant, oldPlan: TenantPlan): Promise<void> {
    console.log(`Sending plan upgrade notification to ${tenant.billing.billingEmail}`)
  }

  async sendSuspensionNotification(tenant: Tenant, reason: string): Promise<void> {
    console.log(`Sending suspension notification to ${tenant.billing.billingEmail}`)
  }

  async sendReactivationNotification(tenant: Tenant): Promise<void> {
    console.log(`Sending reactivation notification to ${tenant.billing.billingEmail}`)
  }

  async sendTerminationNotification(tenant: Tenant, reason: string): Promise<void> {
    console.log(`Sending termination notification to ${tenant.billing.billingEmail}`)
  }
}

class MockTenantProvisioningService implements TenantProvisioningService {
  async provisionTenantResources(tenant: Tenant): Promise<OnboardingStep> {
    console.log(`Provisioning resources for tenant ${tenant.id} (${tenant.plan})`)

    // Simulate resource provisioning
    await new Promise(resolve => setTimeout(resolve, 1000))

    return {
      step: 'PROVISION_RESOURCES',
      status: 'COMPLETED',
      message: 'Resources provisioned successfully',
      timestamp: new Date()
    }
  }
}

class MockTenantMonitoringService implements TenantMonitoringService {
  async disableTenantAccess(tenantId: string): Promise<void> {
    console.log(`Disabling access for tenant ${tenantId}`)
  }

  async enableTenantAccess(tenantId: string): Promise<void> {
    console.log(`Enabling access for tenant ${tenantId}`)
  }

  async archiveTenantData(tenantId: string): Promise<void> {
    console.log(`Archiving data for tenant ${tenantId}`)
  }
}

// Usage Example
async function tenantManagementExample() {
  console.log('=== Tenant Management System ===\n')

  // Setup services
  const tenantRepository = new InMemoryTenantRepository()
  const notificationService = new MockNotificationService()
  const provisioningService = new MockTenantProvisioningService()
  const monitoringService = new MockTenantMonitoringService()

  const onboardingService = new TenantOnboardingService(
    tenantRepository,
    notificationService,
    provisioningService
  )

  const lifecycleManager = new TenantLifecycleManager(
    tenantRepository,
    monitoringService,
    notificationService
  )

  // Onboard new tenants
  console.log('1. Onboarding New Tenants')

  const basicTenantRequest: TenantOnboardingRequest = {
    name: 'Small Business Inc',
    domain: 'smallbusiness.app',
    adminEmail: '[email protected]',
    plan: 'basic',
    companySize: '1-10',
    industry: 'Technology'
  }

  const basicResult = await onboardingService.onboardTenant(basicTenantRequest)
  console.log(`Basic tenant onboarded: ${basicResult.tenant.name} (${basicResult.tenant.id})`)

  const enterpriseTenantRequest: TenantOnboardingRequest = {
    name: 'Enterprise Corp',
    domain: 'enterprise.corp',
    adminEmail: '[email protected]',
    plan: 'enterprise',
    companySize: '1000+',
    industry: 'Finance'
  }

  const enterpriseResult = await onboardingService.onboardTenant(enterpriseTenantRequest)
  console.log(`Enterprise tenant onboarded: ${enterpriseResult.tenant.name} (${enterpriseResult.tenant.id})`)

  // Display onboarding steps
  console.log('\nOnboarding steps for ${basicResult.tenant.name}:')
  basicResult.onboardingSteps.forEach(step => {
    console.log(`  ${step.step}: ${step.status} - ${step.message}`)
  })

  // List all active tenants
  console.log('\n2. Active Tenants')
  const activeTenants = await tenantRepository.findActiveTenants()
  activeTenants.forEach(tenant => {
    console.log(`  ${tenant.name} (${tenant.plan}) - ${tenant.domain}`)
  })

  // Upgrade tenant plan
  console.log('\n3. Upgrading Tenant Plan')
  const upgradedTenant = await lifecycleManager.upgradeTenantPlan(
    basicResult.tenant.id,
    TenantPlan.PREMIUM
  )
  console.log(`Upgraded ${upgradedTenant.name} to ${upgradedTenant.plan}`)

  // Suspend tenant
  console.log('\n4. Suspending Tenant')
  const suspendedTenant = await lifecycleManager.suspendTenant(
    enterpriseResult.tenant.id,
    'Payment overdue'
  )
  console.log(`Suspended ${suspendedTenant.name} for: Payment overdue`)

  // Reactivate tenant
  console.log('\n5. Reactivating Tenant')
  await new Promise(resolve => setTimeout(resolve, 1000)) // Simulate payment
  const reactivatedTenant = await lifecycleManager.reactivateTenant(suspendedTenant.id)
  console.log(`Reactivated ${reactivatedTenant.name}`)

  // Get tenant statistics
  console.log('\n6. Tenant Statistics')
  const allTenants = await tenantRepository.findAll()
  const statsByPlan = allTenants.reduce((acc, tenant) => {
    acc[tenant.plan] = (acc[tenant.plan] || 0) + 1
    return acc
  }, {} as Record<string, number>)

  console.log('Tenants by plan:', statsByPlan)

  const statsByStatus = allTenants.reduce((acc, tenant) => {
    acc[tenant.status] = (acc[tenant.status] || 0) + 1
    return acc
  }, {} as Record<string, number>)

  console.log('Tenants by status:', statsByStatus)
}

export {
  Tenant,
  TenantPlan,
  TenantStatus,
  TenantSettings,
  BillingInfo,
  ResourceLimits,
  NotificationSettings,
  ITenantRepository,
  InMemoryTenantRepository,
  TenantOnboardingService,
  TenantLifecycleManager,
  TenantOnboardingRequest,
  OnboardingStep,
  MockNotificationService,
  MockTenantProvisioningService,
  MockTenantMonitoringService,
  tenantManagementExample
}
        

💻 Ressourcenquoten und Limits

🔴 complex ⭐⭐⭐⭐

Implementierung von Ressourcenquoten-Management mit Monitoring, Alerts und Durchsetzung

⏱️ 50 min 🏷️ resource-quotas, limits-management, usage-monitoring
Prerequisites: Monitoring systems knowledge, Rate limiting concepts

// Resource Quota Management System

// Resource Types and Quota Definitions
enum ResourceType {
  USERS = 'users',
  STORAGE = 'storage',
  API_CALLS = 'api_calls',
  PROJECTS = 'projects',
  INTEGRATIONS = 'integrations',
  BANDWIDTH = 'bandwidth'
}

interface ResourceQuota {
  resourceType: ResourceType
  limit: number
  current: number
  period?: 'monthly' | 'daily' | 'yearly'
  resetDate?: Date
  unit: string
}

interface TenantQuota {
  tenantId: string
  quotas: Map<ResourceType, ResourceQuota>
  lastUpdated: Date
}

interface UsageRecord {
  tenantId: string
  resourceType: ResourceType
  amount: number
  timestamp: Date
  metadata?: Record<string, any>
}

interface QuotaAlert {
  tenantId: string
  resourceType: ResourceType
  alertType: 'WARNING' | 'CRITICAL' | 'EXCEEDED'
  currentUsage: number
  limit: number
  percentage: number
  timestamp: Date
}

// Resource Usage Tracker
class ResourceUsageTracker {
  private usageRecords: Map<string, UsageRecord[]> = new Map()
  private tenantQuotas: Map<string, TenantQuota> = new Map()

  // Record resource usage
  recordUsage(tenantId: string, resourceType: ResourceType, amount: number, metadata?: Record<string, any>): void {
    const record: UsageRecord = {
      tenantId,
      resourceType,
      amount,
      timestamp: new Date(),
      metadata
    }

    const key = `${tenantId}:${resourceType}`
    if (!this.usageRecords.has(key)) {
      this.usageRecords.set(key, [])
    }

    this.usageRecords.get(key)!.push(record)

    // Keep only last 10000 records per resource per tenant
    const records = this.usageRecords.get(key)!
    if (records.length > 10000) {
      records.splice(0, records.length - 10000)
    }
  }

  // Get current usage for a specific resource
  getCurrentUsage(tenantId: string, resourceType: ResourceType, period?: 'daily' | 'monthly'): number {
    const key = `${tenantId}:${resourceType}`
    const records = this.usageRecords.get(key) || []

    if (!period) {
      // Return total usage
      return records.reduce((total, record) => total + record.amount, 0)
    }

    const now = new Date()
    const cutoffDate = this.getCutoffDate(now, period)

    return records
      .filter(record => record.timestamp >= cutoffDate)
      .reduce((total, record) => total + record.amount, 0)
  }

  // Get usage trend for analytics
  getUsageTrend(tenantId: string, resourceType: ResourceType, days: number = 30): Array<{
    date: string
    usage: number
  }> {
    const key = `${tenantId}:${resourceType}`
    const records = this.usageRecords.get(key) || []

    const endDate = new Date()
    const startDate = new Date(endDate.getTime() - (days * 24 * 60 * 60 * 1000))

    // Group usage by day
    const dailyUsage: Record<string, number> = {}

    for (const record of records) {
      if (record.timestamp >= startDate && record.timestamp <= endDate) {
        const dateKey = record.timestamp.toISOString().split('T')[0]
        dailyUsage[dateKey] = (dailyUsage[dateKey] || 0) + record.amount
      }
    }

    // Fill missing days with 0 usage
    const trend: Array<{ date: string; usage: number }> = []
    for (let date = new Date(startDate); date <= endDate; date.setDate(date.getDate() + 1)) {
      const dateKey = date.toISOString().split('T')[0]
      trend.push({
        date: dateKey,
        usage: dailyUsage[dateKey] || 0
      })
    }

    return trend
  }

  // Get top resource consumers
  getTopConsumers(resourceType: ResourceType, limit: number = 10): Array<{
    tenantId: string
    usage: number
  }> {
    const tenantUsage: Record<string, number> = {}

    for (const [key, records] of this.usageRecords.entries()) {
      if (key.endsWith(`:${resourceType}`)) {
        const tenantId = key.split(':')[0]
        const usage = records.reduce((total, record) => total + record.amount, 0)
        tenantUsage[tenantId] = usage
      }
    }

    return Object.entries(tenantUsage)
      .map(([tenantId, usage]) => ({ tenantId, usage }))
      .sort((a, b) => b.usage - a.usage)
      .slice(0, limit)
  }

  private getCutoffDate(date: Date, period: 'daily' | 'monthly'): Date {
    if (period === 'daily') {
      const cutoff = new Date(date)
      cutoff.setHours(0, 0, 0, 0)
      return cutoff
    } else if (period === 'monthly') {
      const cutoff = new Date(date.getFullYear(), date.getMonth(), 1)
      return cutoff
    }

    return new Date(0)
  }

  // Set tenant quota
  setTenantQuota(tenantId: string, quotas: Map<ResourceType, ResourceQuota>): void {
    this.tenantQuotas.set(tenantId, {
      tenantId,
      quotas: new Map(quotas),
      lastUpdated: new Date()
    })
  }

  // Get tenant quota
  getTenantQuota(tenantId: string): TenantQuota | null {
    return this.tenantQuotas.get(tenantId) || null
  }
}

// Quota Enforcement Engine
class QuotaEnforcementEngine {
  private alertThresholds: Map<ResourceType, { warning: number; critical: number }> = new Map([
    [ResourceType.USERS, { warning: 0.8, critical: 0.95 }],
    [ResourceType.STORAGE, { warning: 0.8, critical: 0.95 }],
    [ResourceType.API_CALLS, { warning: 0.8, critical: 0.95 }],
    [ResourceType.PROJECTS, { warning: 0.8, critical: 0.95 }],
    [ResourceType.INTEGRATIONS, { warning: 0.8, critical: 0.95 }],
    [ResourceType.BANDWIDTH, { warning: 0.8, critical: 0.95 }]
  ])

  constructor(
    private usageTracker: ResourceUsageTracker,
    private alertService: QuotaAlertService,
    private enforcementActions: QuotaEnforcementActions
  ) {}

  // Check if action can be performed (soft check)
  canPerformAction(
    tenantId: string,
    resourceType: ResourceType,
    additionalAmount: number = 1
  ): {
    allowed: boolean
    currentUsage: number
    limit: number
    remaining: number
    percentageUsed: number
  } {
    const quota = this.usageTracker.getTenantQuota(tenantId)
    if (!quota) {
      return { allowed: false, currentUsage: 0, limit: 0, remaining: 0, percentageUsed: 100 }
    }

    const resourceQuota = quota.quotas.get(resourceType)
    if (!resourceQuota) {
      return { allowed: false, currentUsage: 0, limit: 0, remaining: 0, percentageUsed: 100 }
    }

    const currentUsage = this.getCurrentUsage(tenantId, resourceType, resourceQuota.period)
    const newTotalUsage = currentUsage + additionalAmount
    const remaining = Math.max(0, resourceQuota.limit - newTotalUsage)
    const percentageUsed = (newTotalUsage / resourceQuota.limit) * 100

    const allowed = newTotalUsage <= resourceQuota.limit

    return {
      allowed,
      currentUsage,
      limit: resourceQuota.limit,
      remaining,
      percentageUsed
    }
  }

  // Enforce quota check (hard check with potential action)
  async enforceQuota(
    tenantId: string,
    resourceType: ResourceType,
    additionalAmount: number = 1,
    actionContext?: Record<string, any>
  ): Promise<{
    allowed: boolean
    reason?: string
    action?: string
  }> {
    const check = this.canPerformAction(tenantId, resourceType, additionalAmount)

    if (!check.allowed) {
      // Quota exceeded
      await this.handleQuotaExceeded(tenantId, resourceType, check, actionContext)

      return {
        allowed: false,
        reason: `Quota exceeded for ${resourceType}. Current: ${check.currentUsage}, Limit: ${check.limit}`,
        action: await this.enforcementActions.getEnforcementAction(tenantId, resourceType, check.percentageUsed)
      }
    }

    // Check for approaching limits
    await this.checkApproachingLimits(tenantId, resourceType, check)

    // Record the usage
    this.usageTracker.recordUsage(tenantId, resourceType, additionalAmount, actionContext)

    return { allowed: true }
  }

  // Reset quotas that are period-based
  async resetPeriodicQuotas(): Promise<void> {
    const now = new Date()
    const tenants = await this.getAllTenants()

    for (const tenant of tenants) {
      const quota = this.usageTracker.getTenantQuota(tenant.id)
      if (!quota) continue

      for (const [resourceType, resourceQuota] of quota.quotas.entries()) {
        if (resourceQuota.period && resourceQuota.resetDate && now >= resourceQuota.resetDate) {
          await this.resetQuota(tenant.id, resourceType)
        }
      }
    }
  }

  private async handleQuotaExceeded(
    tenantId: string,
    resourceType: ResourceType,
    check: any,
    context?: Record<string, any>
  ): Promise<void> {
    await this.alertService.sendQuotaAlert({
      tenantId,
      resourceType,
      alertType: 'EXCEEDED',
      currentUsage: check.currentUsage,
      limit: check.limit,
      percentage: check.percentageUsed,
      timestamp: new Date()
    })

    await this.enforcementActions.executeEnforcementAction(tenantId, resourceType, 'EXCEEDED', context)
  }

  private async checkApproachingLimits(
    tenantId: string,
    resourceType: ResourceType,
    check: any
  ): Promise<void> {
    const thresholds = this.alertThresholds.get(resourceType)
    if (!thresholds) return

    const { warning, critical } = thresholds

    if (check.percentageUsed >= critical) {
      await this.alertService.sendQuotaAlert({
        tenantId,
        resourceType,
        alertType: 'CRITICAL',
        currentUsage: check.currentUsage,
        limit: check.limit,
        percentage: check.percentageUsed,
        timestamp: new Date()
      })
    } else if (check.percentageUsed >= warning) {
      await this.alertService.sendQuotaAlert({
        tenantId,
        resourceType,
        alertType: 'WARNING',
        currentUsage: check.currentUsage,
        limit: check.limit,
        percentage: check.percentageUsed,
        timestamp: new Date()
      })
    }
  }

  private getCurrentUsage(tenantId: string, resourceType: ResourceType, period?: string): number {
    if (period === 'monthly') {
      return this.usageTracker.getCurrentUsage(tenantId, resourceType, 'monthly')
    } else if (period === 'daily') {
      return this.usageTracker.getCurrentUsage(tenantId, resourceType, 'daily')
    }
    return this.usageTracker.getCurrentUsage(tenantId, resourceType)
  }

  private async resetQuota(tenantId: string, resourceType: ResourceType): Promise<void> {
    const quota = this.usageTracker.getTenantQuota(tenantId)
    if (!quota) return

    const resourceQuota = quota.quotas.get(resourceType)
    if (!resourceQuota || !resourceQuota.period) return

    // Update reset date for next period
    const now = new Date()
    let nextResetDate: Date

    if (resourceQuota.period === 'monthly') {
      nextResetDate = new Date(now.getFullYear(), now.getMonth() + 1, 1)
    } else if (resourceQuota.period === 'daily') {
      nextResetDate = new Date(now.getTime() + 24 * 60 * 60 * 1000)
    } else {
      return
    }

    resourceQuota.resetDate = nextResetDate

    console.log(`Reset quota for tenant ${tenantId}, resource ${resourceType}`)
  }

  private async getAllTenants(): Promise<Array<{ id: string }>> {
    // In real implementation, this would query the tenant repository
    // For demo, return sample data
    return [
      { id: 'tenant-1' },
      { id: 'tenant-2' },
      { id: 'tenant-3' }
    ]
  }
}

// Quota Alert Service
interface QuotaAlertService {
  sendQuotaAlert(alert: QuotaAlert): Promise<void>
}

class EmailQuotaAlertService implements QuotaAlertService {
  async sendQuotaAlert(alert: QuotaAlert): Promise<void> {
    const alertLevel = alert.alertType.toLowerCase()
    const subject = `Quota ${alertLevel}: ${alert.resourceType} for tenant ${alert.tenantId}`

    console.log(`=== EMAIL ALERT ===`)
    console.log(`To: admin@${alert.tenantId}.com`)
    console.log(`Subject: ${subject}`)
    console.log(`Message: `)
    console.log(`  Resource: ${alert.resourceType}`)
    console.log(`  Current Usage: ${alert.currentUsage}`)
    console.log(`  Limit: ${alert.limit}`)
    console.log(`  Usage: ${alert.percentage.toFixed(1)}%`)
    console.log(`  Alert Level: ${alert.alertType}`)
    console.log(`================`)
  }
}

// Quota Enforcement Actions
interface QuotaEnforcementActions {
  getEnforcementAction(tenantId: string, resourceType: ResourceType, percentageUsed: number): Promise<string>
  executeEnforcementAction(tenantId: string, resourceType: ResourceType, alertType: string, context?: Record<string, any>): Promise<void>
}

class DefaultQuotaEnforcementActions implements QuotaEnforcementActions {
  async getEnforcementAction(tenantId: string, resourceType: ResourceType, percentageUsed: number): Promise<string> {
    if (percentageUsed >= 100) {
      switch (resourceType) {
        case ResourceType.API_CALLS:
          return 'THROTTLE_API'
        case ResourceType.STORAGE:
          return 'BLOCK_UPLOADS'
        case ResourceType.USERS:
          return 'BLOCK_USER_CREATION'
        case ResourceType.BANDWIDTH:
          return 'THROTTLE_BANDWIDTH'
        default:
          return 'BLOCK_ACTION'
      }
    }

    return 'ALLOW_WITH_WARNING'
  }

  async executeEnforcementAction(
    tenantId: string,
    resourceType: ResourceType,
    alertType: string,
    context?: Record<string, any>
  ): Promise<void> {
    const action = await this.getEnforcementAction(tenantId, resourceType, 100)

    console.log(`=== ENFORCEMENT ACTION ===`)
    console.log(`Tenant: ${tenantId}`)
    console.log(`Resource: ${resourceType}`)
    console.log(`Alert Type: ${alertType}`)
    console.log(`Action: ${action}`)

    switch (action) {
      case 'THROTTLE_API':
        console.log(`→ API requests will be throttled for tenant ${tenantId}`)
        break
      case 'BLOCK_UPLOADS':
        console.log(`→ File uploads blocked for tenant ${tenantId}`)
        break
      case 'BLOCK_USER_CREATION':
        console.log(`→ New user creation blocked for tenant ${tenantId}`)
        break
      case 'THROTTLE_BANDWIDTH':
        console.log(`→ Bandwidth throttled for tenant ${tenantId}`)
        break
      case 'BLOCK_ACTION':
        console.log(`→ Action blocked for tenant ${tenantId}`)
        break
    }
    console.log(`=========================`)
  }
}

// Quota Analytics Dashboard
class QuotaAnalytics {
  constructor(private usageTracker: ResourceUsageTracker) {}

  // Get quota utilization report
  async getQuotaUtilizationReport(tenantId: string): Promise<{
    resourceUtilization: Array<{
      resourceType: ResourceType
      current: number
      limit: number
      percentage: number
      status: 'HEALTHY' | 'WARNING' | 'CRITICAL' | 'EXCEEDED'
    }>
    overallHealth: 'HEALTHY' | 'WARNING' | 'CRITICAL'
    recommendations: string[]
  }> {
    const quota = this.usageTracker.getTenantQuota(tenantId)
    if (!quota) {
      throw new Error(`No quota found for tenant ${tenantId}`)
    }

    const resourceUtilization: Array<{
      resourceType: ResourceType
      current: number
      limit: number
      percentage: number
      status: 'HEALTHY' | 'WARNING' | 'CRITICAL' | 'EXCEEDED'
    }> = []

    for (const [resourceType, resourceQuota] of quota.quotas.entries()) {
      const current = this.usageTracker.getCurrentUsage(
        tenantId,
        resourceType,
        resourceQuota.period as any
      )
      const percentage = (current / resourceQuota.limit) * 100

      let status: 'HEALTHY' | 'WARNING' | 'CRITICAL' | 'EXCEEDED'
      if (percentage >= 100) {
        status = 'EXCEEDED'
      } else if (percentage >= 95) {
        status = 'CRITICAL'
      } else if (percentage >= 80) {
        status = 'WARNING'
      } else {
        status = 'HEALTHY'
      }

      resourceUtilization.push({
        resourceType,
        current,
        limit: resourceQuota.limit,
        percentage,
        status
      })
    }

    const overallHealth = this.calculateOverallHealth(resourceUtilization)
    const recommendations = this.generateRecommendations(resourceUtilization)

    return {
      resourceUtilization,
      overallHealth,
      recommendations
    }
  }

  // Get capacity planning insights
  async getCapacityPlanningInsights(days: number = 30): Promise<{
    resourceGrowth: Record<ResourceType, number>
    tenantGrowth: Record<string, number>
    predictedExhaustion: Array<{
      tenantId: string
      resourceType: ResourceType
      estimatedDaysToExhaustion: number
    }>
  }> {
    const resourceGrowth: Record<string, number> = {}
    const tenantGrowth: Record<string, number> = {}
    const predictedExhaustion: Array<{
      tenantId: string
      resourceType: ResourceType
      estimatedDaysToExhaustion: number
    }> = []

    // Calculate growth rates for each resource type
    for (const resourceType of Object.values(ResourceType)) {
      const trend = this.usageTracker.getUsageTrend('sample-tenant', resourceType as ResourceType, days)
      if (trend.length > 1) {
        const growth = ((trend[trend.length - 1].usage - trend[0].usage) / trend[0].usage) * 100
        resourceGrowth[resourceType] = growth
      }
    }

    // Predict exhaustion for tenants approaching limits
    // This is a simplified prediction algorithm
    const sampleTenants = ['tenant-1', 'tenant-2', 'tenant-3']

    for (const tenantId of sampleTenants) {
      const quota = this.usageTracker.getTenantQuota(tenantId)
      if (!quota) continue

      for (const [resourceType, resourceQuota] of quota.quotas.entries()) {
        const current = this.usageTracker.getCurrentUsage(tenantId, resourceType, resourceQuota.period as any)
        const remaining = resourceQuota.limit - current

        if (remaining > 0) {
          const trend = this.usageTracker.getUsageTrend(tenantId, resourceType, 7) // Last 7 days
          if (trend.length > 1) {
            const dailyUsage = (trend[trend.length - 1].usage - trend[0].usage) / trend.length
            if (dailyUsage > 0) {
              const daysToExhaustion = Math.floor(remaining / dailyUsage)
              if (daysToExhaustion < 30) { // Only show if exhausted within 30 days
                predictedExhaustion.push({
                  tenantId,
                  resourceType,
                  estimatedDaysToExhaustion: daysToExhaustion
                })
              }
            }
          }
        }
      }
    }

    return {
      resourceGrowth,
      tenantGrowth,
      predictedExhaustion
    }
  }

  private calculateOverallHealth(utilization: any[]): 'HEALTHY' | 'WARNING' | 'CRITICAL' {
    const criticalCount = utilization.filter(u => u.status === 'CRITICAL' || u.status === 'EXCEEDED').length
    const warningCount = utilization.filter(u => u.status === 'WARNING').length

    if (criticalCount > 0) {
      return 'CRITICAL'
    } else if (warningCount > 0) {
      return 'WARNING'
    } else {
      return 'HEALTHY'
    }
  }

  private generateRecommendations(utilization: any[]): string[] {
    const recommendations: string[] = []

    for (const util of utilization) {
      if (util.percentage >= 80) {
        switch (util.resourceType) {
          case ResourceType.STORAGE:
            recommendations.push('Consider upgrading your plan or archiving old files to free up storage space.')
            break
          case ResourceType.API_CALLS:
            recommendations.push('Optimize API usage or upgrade to a plan with higher API limits.')
            break
          case ResourceType.USERS:
            recommendations.push('Upgrade your plan to accommodate more users or remove inactive accounts.')
            break
          case ResourceType.BANDWIDTH:
            recommendations.push('Monitor bandwidth usage and consider upgrading for higher limits.')
            break
        }
      }
    }

    if (recommendations.length === 0) {
      recommendations.push('Your resource usage is within healthy limits.')
    }

    return recommendations
  }
}

// Usage Example
async function resourceQuotaExample() {
  console.log('=== Resource Quota Management System ===\n')

  // Initialize services
  const usageTracker = new ResourceUsageTracker()
  const alertService = new EmailQuotaAlertService()
  const enforcementActions = new DefaultQuotaEnforcementActions()
  const quotaEngine = new QuotaEnforcementEngine(usageTracker, alertService, enforcementActions)
  const analytics = new QuotaAnalytics(usageTracker)

  // Set up tenant quotas
  console.log('1. Setting up tenant quotas')

  const tenantQuotas = new Map<ResourceType, ResourceQuota>([
    [ResourceType.USERS, { resourceType: ResourceType.USERS, limit: 100, current: 0, unit: 'users' }],
    [ResourceType.STORAGE, { resourceType: ResourceType.STORAGE, limit: 1000, current: 0, unit: 'MB', period: 'monthly' }],
    [ResourceType.API_CALLS, { resourceType: ResourceType.API_CALLS, limit: 10000, current: 0, unit: 'calls', period: 'monthly' }],
    [ResourceType.PROJECTS, { resourceType: ResourceType.PROJECTS, limit: 50, current: 0, unit: 'projects' }]
  ])

  usageTracker.setTenantQuota('tenant-demo', tenantQuotas)

  // Simulate usage
  console.log('\n2. Simulating resource usage')

  // Add some users
  for (let i = 0; i < 75; i++) {
    await quotaEngine.enforceQuota('tenant-demo', ResourceType.USERS, 1, { action: 'create_user' })
  }

  // Simulate API calls (gradually approaching limit)
  for (let i = 0; i < 8500; i++) {
    await quotaEngine.enforceQuota('tenant-demo', ResourceType.API_CALLS, 1, { endpoint: '/api/data' })
  }

  // Add storage usage
  for (let i = 0; i < 850; i++) {
    await quotaEngine.enforceQuota('tenant-demo', ResourceType.STORAGE, 1, { file_size: 1024 * 1024 })
  }

  // Check quota status
  console.log('\n3. Current quota status')

  const currentQuota = usageTracker.getTenantQuota('tenant-demo')!
  for (const [resourceType, quota] of currentQuota.quotas.entries()) {
    const current = usageTracker.getCurrentUsage('tenant-demo', resourceType, quota.period as any)
    const percentage = (current / quota.limit) * 100
    console.log(`${resourceType}: ${current}/${quota.limit} (${percentage.toFixed(1)}%)`)
  }

  // Test quota enforcement
  console.log('\n4. Testing quota enforcement')

  const userCheck = quotaEngine.canPerformAction('tenant-demo', ResourceType.USERS, 30)
  console.log('Can add 30 users:', userCheck.allowed, '- Remaining:', userCheck.remaining)

  const apiCheck = quotaEngine.canPerformAction('tenant-demo', ResourceType.API_CALLS, 2000)
  console.log('Can make 2000 API calls:', apiCheck.allowed, '- Remaining:', apiCheck.remaining)

  // Try to exceed quota
  console.log('\n5. Attempting to exceed quotas')

  try {
    await quotaEngine.enforceQuota('tenant-demo', ResourceType.API_CALLS, 2000, { endpoint: '/api/bulk-data' })
    console.log('Bulk API call succeeded')
  } catch (error) {
    console.log('Bulk API call blocked:', error)
  }

  // Generate quota report
  console.log('\n6. Quota utilization report')

  const report = await analytics.getQuotaUtilizationReport('tenant-demo')
  console.log('Overall health:', report.overallHealth)
  console.log('\nResource utilization:')
  report.resourceUtilization.forEach(util => {
    console.log(`  ${util.resourceType}: ${util.percentage.toFixed(1)}% (${util.status})`)
  })

  if (report.recommendations.length > 0) {
    console.log('\nRecommendations:')
    report.recommendations.forEach(rec => console.log(`  • ${rec}`))
  }

  // Capacity planning insights
  console.log('\n7. Capacity planning insights')

  const insights = await analytics.getCapacityPlanningInsights(30)
  console.log('Resource growth rates:')
  Object.entries(insights.resourceGrowth).forEach(([resource, growth]) => {
    console.log(`  ${resource}: ${growth.toFixed(1)}%`)
  })

  if (insights.predictedExhaustion.length > 0) {
    console.log('\nResources approaching limits:')
    insights.predictedExhaustion.forEach(pred => {
      console.log(`  ${pred.tenantId} - ${pred.resourceType}: ${pred.estimatedDaysToExhaustion} days`)
    })
  } else {
    console.log('\nNo resources predicted to exhaust within 30 days')
  }
}

export {
  ResourceType,
  ResourceQuota,
  TenantQuota,
  UsageRecord,
  QuotaAlert,
  ResourceUsageTracker,
  QuotaEnforcementEngine,
  QuotaAlertService,
  EmailQuotaAlertService,
  QuotaEnforcementActions,
  DefaultQuotaEnforcementActions,
  QuotaAnalytics,
  resourceQuotaExample
}
        

💻 White-Label Lösungen

🔴 complex ⭐⭐⭐⭐⭐

Implementierung anpassbarer White-Label Lösungen mit Branding, Themes und Multi-Marke-Unterstützung

⏱️ 70 min 🏷️ white-label-solutions, brand-customization, theme-management
Prerequisites: Frontend development knowledge, CSS/JavaScript understanding

// White-Label Solution Management System

// Brand Configuration
interface BrandConfig {
  tenantId: string
  brandId: string
  name: string
  domain: string
  logo: {
    primary: string
    secondary?: string
    favicon?: string
    emailLogo?: string
  }
  colors: {
    primary: string
    secondary: string
    accent: string
    background: string
    text: string
    error: string
    success: string
    warning: string
  }
  typography: {
    fontFamily: string
    headingFont?: string
    baseFontSize: string
    scale: number[]
  }
  layout: {
    headerHeight: string
    sidebarWidth: string
    borderRadius: string
    spacing: {
      small: string
      medium: string
      large: string
    }
  }
  customCSS?: string
  customJS?: string
  features: {
    showBranding: boolean
    allowCustomDomain: boolean
    customLogin: boolean
    customEmails: boolean
    whiteLabelReports: boolean
  }
  integration: {
    analytics: AnalyticsConfig
    support: SupportConfig
    notifications: NotificationConfig
  }
  createdAt: Date
  updatedAt: Date
}

interface AnalyticsConfig {
  provider: 'google' | 'mixpanel' | 'segment' | 'custom'
  trackingId: string
  customScript?: string
}

interface SupportConfig {
  provider: 'intercom' | 'zendesk' | 'custom'
  apiKey: string
  customWidget?: string
}

interface NotificationConfig {
  emailProvider: 'sendgrid' | 'ses' | 'mailgun' | 'custom'
  smsProvider: 'twilio' | 'custom'
  templates: EmailTemplates
}

interface EmailTemplates {
  welcome: EmailTemplate
  passwordReset: EmailTemplate
  invitation: EmailTemplate
  billing: EmailTemplate
  support: EmailTemplate
}

interface EmailTemplate {
  subject: string
  htmlContent: string
  textContent?: string
  variables: string[]
}

// Theme Management Service
class ThemeManagementService {
  private themes: Map<string, BrandConfig> = new Map()
  private defaultTheme: BrandConfig

  constructor() {
    this.defaultTheme = this.createDefaultTheme()
  }

  // Create or update brand configuration
  async saveBrandConfig(brandConfig: BrandConfig): Promise<BrandConfig> {
    // Validate brand configuration
    this.validateBrandConfig(brandConfig)

    // Generate CSS theme
    const themeCSS = this.generateThemeCSS(brandConfig)

    // Store the configuration
    this.themes.set(brandConfig.brandId, {
      ...brandConfig,
      updatedAt: new Date()
    })

    console.log(`Saved brand config for ${brandConfig.name} (${brandConfig.brandId})`)
    return brandConfig
  }

  // Get brand configuration
  getBrandConfig(brandId: string): BrandConfig | null {
    return this.themes.get(brandId) || null
  }

  // Get brand configuration by domain
  getBrandConfigByDomain(domain: string): BrandConfig | null {
    for (const brand of this.themes.values()) {
      if (brand.domain === domain) {
        return brand
      }
    }
    return null
  }

  // Generate CSS theme from brand configuration
  generateThemeCSS(brandConfig: BrandConfig): string {
    const { colors, typography, layout } = brandConfig

    return `
/* Custom Theme for ${brandConfig.name} */
:root {
  /* Colors */
  --primary-color: ${colors.primary};
  --primary-hover: ${this.adjustColor(colors.primary, -10)};
  --primary-light: ${this.adjustColor(colors.primary, 80)};
  --secondary-color: ${colors.secondary};
  --accent-color: ${colors.accent};
  --background-color: ${colors.background};
  --text-color: ${colors.text};
  --text-muted: ${this.adjustColor(colors.text, 60)};
  --border-color: ${this.adjustColor(colors.text, 20)};
  --error-color: ${colors.error};
  --success-color: ${colors.success};
  --warning-color: ${colors.warning};

  /* Typography */
  --font-family: ${typography.fontFamily};
  --heading-font: ${typography.headingFont || typography.fontFamily};
  --font-size-base: ${typography.baseFontSize};
  --font-scale: ${typography.scale.join(', ')};

  /* Layout */
  --header-height: ${layout.headerHeight};
  --sidebar-width: ${layout.sidebarWidth};
  --border-radius: ${layout.borderRadius};
  --spacing-small: ${layout.spacing.small};
  --spacing-medium: ${layout.spacing.medium};
  --spacing-large: ${layout.spacing.large};
}

/* Component Styles */
.btn-primary {
  background-color: var(--primary-color);
  border-color: var(--primary-color);
  color: white;
}

.btn-primary:hover {
  background-color: var(--primary-hover);
  border-color: var(--primary-hover);
}

.card {
  background-color: var(--background-color);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.header {
  height: var(--header-height);
  background-color: var(--primary-color);
  color: white;
}

.sidebar {
  width: var(--sidebar-width);
  background-color: var(--background-color);
  border-right: 1px solid var(--border-color);
}

.text-primary { color: var(--primary-color); }
.text-secondary { color: var(--secondary-color); }
.text-accent { color: var(--accent-color); }
.text-muted { color: var(--text-muted); }

.bg-primary { background-color: var(--primary-color); }
.bg-secondary { background-color: var(--secondary-color); }
.bg-accent { background-color: var(--accent-color); }

/* Custom styles */
${brandConfig.customCSS || ''}
`
  }

  // Generate theme JavaScript for dynamic styling
  generateThemeJS(brandConfig: BrandConfig): string {
    return `
// Dynamic Theme for ${brandConfig.name}
(function() {
  const theme = ${JSON.stringify(brandConfig, null, 2)};

  // Apply theme dynamically
  function applyTheme() {
    const root = document.documentElement;

    // Set CSS custom properties
    Object.entries(theme.colors).forEach(([key, value]) => {
      const cssVar = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}-color`;
      root.style.setProperty(cssVar, value);
    });

    // Set custom fonts
    if (theme.typography.fontFamily) {
      root.style.setProperty('--font-family', theme.typography.fontFamily);
    }

    // Update logos
    const logoElements = document.querySelectorAll('[data-brand-logo]');
    logoElements.forEach(el => {
      el.src = theme.logo.primary;
      el.alt = theme.name;
    });

    // Update favicon
    if (theme.logo.favicon) {
      const favicon = document.querySelector('link[rel="icon"]') || document.createElement('link');
      favicon.rel = 'icon';
      favicon.href = theme.logo.favicon;
      document.head.appendChild(favicon);
    }

    // Update page title
    if (document.title.includes('[BRAND_NAME]')) {
      document.title = document.title.replace('[BRAND_NAME]', theme.name);
    }
  }

  // Apply theme immediately
  applyTheme();

  // Re-apply theme on DOM changes
  const observer = new MutationObserver(applyTheme);
  observer.observe(document.body, { childList: true, subtree: true });

  // Export theme for other components
  window.currentTheme = theme;
})();
${brandConfig.customJS || ''}
`
  }

  // Clone existing theme
  cloneTheme(sourceBrandId: string, newBrandId: string, overrides: Partial<BrandConfig>): BrandConfig {
    const sourceTheme = this.themes.get(sourceBrandId)
    if (!sourceTheme) {
      throw new Error(`Source theme not found: ${sourceBrandId}`)
    }

    const clonedTheme: BrandConfig = {
      ...sourceTheme,
      brandId: newBrandId,
      ...overrides,
      createdAt: new Date(),
      updatedAt: new Date()
    }

    return clonedTheme
  }

  // Reset theme to default
  resetToDefault(brandId: string): BrandConfig {
    const currentTheme = this.themes.get(brandId)
    if (!currentTheme) {
      throw new Error(`Theme not found: ${brandId}`)
    }

    const defaultTheme: BrandConfig = {
      ...this.defaultTheme,
      brandId: currentTheme.brandId,
      tenantId: currentTheme.tenantId,
      name: currentTheme.name,
      domain: currentTheme.domain
    }

    this.themes.set(brandId, defaultTheme)
    return defaultTheme
  }

  // Export theme configuration
  exportTheme(brandId: string): string {
    const theme = this.themes.get(brandId)
    if (!theme) {
      throw new Error(`Theme not found: ${brandId}`)
    }

    return JSON.stringify(theme, null, 2)
  }

  // Import theme configuration
  importTheme(themeJson: string): BrandConfig {
    try {
      const theme = JSON.parse(themeJson) as BrandConfig
      this.validateBrandConfig(theme)
      this.themes.set(theme.brandId, theme)
      return theme
    } catch (error) {
      throw new Error(`Invalid theme configuration: ${error}`)
    }
  }

  private validateBrandConfig(brandConfig: BrandConfig): void {
    if (!brandConfig.brandId || !brandConfig.tenantId || !brandConfig.name) {
      throw new Error('Required fields missing: brandId, tenantId, name')
    }

    // Validate color format (hex)
    const colorFields = Object.values(brandConfig.colors)
    for (const color of colorFields) {
      if (!this.isValidHexColor(color)) {
        throw new Error(`Invalid color format: ${color}`)
      }
    }

    // Validate logo URLs
    if (!this.isValidUrl(brandConfig.logo.primary)) {
      throw new Error(`Invalid primary logo URL: ${brandConfig.logo.primary}`)
    }
  }

  private createDefaultTheme(): BrandConfig {
    return {
      tenantId: 'default',
      brandId: 'default',
      name: 'Default Brand',
      domain: 'app.example.com',
      logo: {
        primary: '/assets/default-logo.png',
        secondary: '/assets/default-logo-light.png',
        favicon: '/assets/default-favicon.ico',
        emailLogo: '/assets/default-email-logo.png'
      },
      colors: {
        primary: '#007bff',
        secondary: '#6c757d',
        accent: '#28a745',
        background: '#ffffff',
        text: '#212529',
        error: '#dc3545',
        success: '#28a745',
        warning: '#ffc107'
      },
      typography: {
        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
        baseFontSize: '16px',
        scale: [0.75, 0.875, 1, 1.125, 1.25, 1.5, 1.875, 2.25, 3]
      },
      layout: {
        headerHeight: '64px',
        sidebarWidth: '250px',
        borderRadius: '4px',
        spacing: {
          small: '8px',
          medium: '16px',
          large: '24px'
        }
      },
      features: {
        showBranding: true,
        allowCustomDomain: false,
        customLogin: false,
        customEmails: false,
        whiteLabelReports: false
      },
      integration: {
        analytics: {
          provider: 'google',
          trackingId: 'GA-DEFAULT'
        },
        support: {
          provider: 'intercom',
          apiKey: 'default-key'
        },
        notifications: {
          emailProvider: 'sendgrid',
          smsProvider: 'twilio',
          templates: this.getDefaultEmailTemplates()
        }
      },
      createdAt: new Date(),
      updatedAt: new Date()
    }
  }

  private getDefaultEmailTemplates(): EmailTemplates {
    return {
      welcome: {
        subject: 'Welcome to {{brand_name}}!',
        htmlContent: `
          <h1>Welcome to {{brand_name}}!</h1>
          <p>Thank you for joining us. Your account has been successfully created.</p>
          <p>Click <a href="{{login_url}}">here</a> to log in.</p>
        `,
        variables: ['brand_name', 'login_url', 'user_name']
      },
      passwordReset: {
        subject: 'Password Reset Request',
        htmlContent: `
          <h1>Password Reset</h1>
          <p>You requested a password reset for your {{brand_name}} account.</p>
          <p>Click <a href="{{reset_url}}">here</a> to reset your password.</p>
        `,
        variables: ['brand_name', 'reset_url', 'user_name']
      },
      invitation: {
        subject: 'Invitation to join {{brand_name}}',
        htmlContent: `
          <h1>You're Invited!</h1>
          <p>You have been invited to join {{brand_name}}.</p>
          <p>Click <a href="{{invite_url}}">here</a> to accept the invitation.</p>
        `,
        variables: ['brand_name', 'invite_url', 'inviter_name', 'team_name']
      },
      billing: {
        subject: 'Invoice from {{brand_name}}',
        htmlContent: `
          <h1>Invoice {{invoice_number}}</h1>
          <p>Your invoice from {{brand_name}} is ready.</p>
          <p>Amount: {{amount}} {{currency}}</p>
          <p>Due date: {{due_date}}</p>
        `,
        variables: ['brand_name', 'invoice_number', 'amount', 'currency', 'due_date']
      },
      support: {
        subject: 'Support Ticket Update - {{ticket_id}}',
        htmlContent: `
          <h1>Support Ticket Update</h1>
          <p>Your support ticket {{ticket_id}} has been updated.</p>
          <p>Message: {{message}}</p>
        `,
        variables: ['brand_name', 'ticket_id', 'message', 'support_agent']
      }
    }
  }

  private isValidHexColor(color: string): boolean {
    return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color)
  }

  private isValidUrl(url: string): boolean {
    try {
      new URL(url)
      return true
    } catch {
      return false
    }
  }

  private adjustColor(color: string, amount: number): string {
    const num = parseInt(color.replace('#', ''), 16)
    const r = Math.min(255, Math.max(0, (num >> 16) + amount))
    const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + amount))
    const b = Math.min(255, Math.max(0, (num & 0x0000FF) + amount))
    return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
  }
}

// Brand Middleware for Web Applications
class BrandMiddleware {
  constructor(private themeService: ThemeManagementService) {}

  // Express.js middleware example
  expressMiddleware() {
    return (req: any, res: any, next: any) => {
      const domain = req.hostname
      const brand = this.themeService.getBrandConfigByDomain(domain)

      if (brand) {
        req.brand = brand
        res.locals.brand = brand

        // Set brand-specific headers
        res.setHeader('X-Brand-ID', brand.brandId)
        res.setHeader('X-Brand-Name', brand.name)

        // Inject brand variables into templates
        res.locals.theme = {
          css: this.themeService.generateThemeCSS(brand),
          js: this.themeService.generateThemeJS(brand)
        }
      } else {
        // Use default theme
        req.brand = this.themeService.getBrandConfig('default')
        res.locals.brand = req.brand
        res.locals.theme = {
          css: this.themeService.generateThemeCSS(req.brand),
          js: this.themeService.generateThemeJS(req.brand)
        }
      }

      next()
    }
  }

  // Generate HTML with brand styling
  generateBrandedHTML(template: string, brand: BrandConfig): string {
    const themeCSS = this.themeService.generateThemeCSS(brand)
    const themeJS = this.themeService.generateThemeJS(brand)

    return template
      .replace('{{BRAND_CSS}}', themeCSS)
      .replace('{{BRAND_JS}}', themeJS)
      .replace('{{BRAND_NAME}}', brand.name)
      .replace('{{BRAND_LOGO}}', brand.logo.primary)
      .replace('{{FAVICON}}', brand.logo.favicon || '')
      .replace('{{PRIMARY_COLOR}}', brand.colors.primary)
      .replace('{{SECONDARY_COLOR}}', brand.colors.secondary)
  }
}

// Email Template Service for White-Label
class WhiteLabelEmailService {
  constructor(private themeService: ThemeManagementService) {}

  // Generate branded email template
  generateBrandedEmail(
    brandId: string,
    templateType: keyof EmailTemplates,
    variables: Record<string, any>
  ): {
    subject: string
    htmlContent: string
    textContent?: string
  } {
    const brand = this.themeService.getBrandConfig(brandId)
    if (!brand) {
      throw new Error(`Brand not found: ${brandId}`)
    }

    const template = brand.integration.notifications.templates[templateType]

    // Replace template variables
    let subject = template.subject
    let htmlContent = template.htmlContent
    let textContent = template.textContent

    for (const [key, value] of Object.entries(variables)) {
      const placeholder = `{{${key}}}`
      subject = subject.replace(new RegExp(placeholder, 'g'), String(value))
      htmlContent = htmlContent.replace(new RegExp(placeholder, 'g'), String(value))
      if (textContent) {
        textContent = textContent.replace(new RegExp(placeholder, 'g'), String(value))
      }
    }

    // Add brand styling to email
    const brandedHTML = `
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="utf-8">
        <title>${subject}</title>
        <style>
          .email-header {
            background-color: ${brand.colors.primary};
            color: white;
            padding: 20px;
            text-align: center;
          }
          .email-body {
            font-family: ${brand.typography.fontFamily};
            color: ${brand.colors.text};
            padding: 20px;
            max-width: 600px;
            margin: 0 auto;
          }
          .email-footer {
            background-color: ${brand.colors.background};
            border-top: 1px solid ${brand.colors.secondary};
            padding: 20px;
            text-align: center;
            color: ${brand.colors.text};
            font-size: 14px;
          }
          .brand-logo {
            max-height: 40px;
            margin-bottom: 10px;
          }
          .btn-primary {
            background-color: ${brand.colors.primary};
            color: white;
            padding: 12px 24px;
            text-decoration: none;
            border-radius: ${brand.layout.borderRadius};
            display: inline-block;
          }
        </style>
      </head>
      <body>
        <div class="email-header">
          <img src="${brand.logo.emailLogo}" alt="${brand.name}" class="brand-logo">
          <h1>${brand.name}</h1>
        </div>
        <div class="email-body">
          ${htmlContent}
        </div>
        <div class="email-footer">
          <p>© ${new Date().getFullYear()} ${brand.name}. All rights reserved.</p>
          <p>This email was sent to {{recipient_email}}</p>
        </div>
      </body>
      </html>
    `

    return {
      subject,
      htmlContent: brandedHTML,
      textContent
    }
  }

  // Send branded email
  async sendBrandedEmail(
    brandId: string,
    to: string[],
    templateType: keyof EmailTemplates,
    variables: Record<string, any>
  ): Promise<void> {
    const email = this.generateBrandedEmail(brandId, templateType, variables)

    console.log(`=== SENDING BRANDED EMAIL ===`)
    console.log(`Brand: ${brandId}`)
    console.log(`To: ${to.join(', ')}`)
    console.log(`Subject: ${email.subject}`)
    console.log(`Template: ${templateType}`)

    // In real implementation, this would integrate with email service
    // await emailProvider.send(email)

    console.log(`Email sent successfully`)
    console.log(`=========================`)
  }
}

// Custom Domain Management
class CustomDomainService {
  private customDomains: Map<string, { tenantId: string; brandId: string; ssl: boolean; verified: boolean }> = new Map()

  // Add custom domain
  async addCustomDomain(tenantId: string, brandId: string, domain: string): Promise<void> {
    if (this.customDomains.has(domain)) {
      throw new Error(`Domain ${domain} is already registered`)
    }

    // Validate domain
    if (!this.isValidDomain(domain)) {
      throw new Error(`Invalid domain: ${domain}`)
    }

    // Generate DNS records for verification
    const dnsRecords = this.generateDNSRecords(domain, tenantId)

    console.log(`=== CUSTOM DOMAIN SETUP ===`)
    console.log(`Domain: ${domain}`)
    console.log(`Tenant: ${tenantId}`)
    console.log(`Brand: ${brandId}`)
    console.log(`\nDNS Records to configure:`)
    console.log(`CNAME: ${domain} -> ${dnsRecords.cname}`)
    console.log(`TXT: ${dnsRecords.txt.name} -> ${dnsRecords.txt.value}`)
    console.log(`========================`)

    // Store domain (pending verification)
    this.customDomains.set(domain, {
      tenantId,
      brandId,
      ssl: false,
      verified: false
    })
  }

  // Verify custom domain
  async verifyCustomDomain(domain: string): Promise<boolean> {
    const domainConfig = this.customDomains.get(domain)
    if (!domainConfig) {
      throw new Error(`Domain not found: ${domain}`)
    }

    // Simulate DNS verification
    console.log(`Verifying domain: ${domain}`)

    // In real implementation, this would check DNS records
    const isVerified = Math.random() > 0.2 // 80% success rate for demo

    if (isVerified) {
      domainConfig.verified = true
      domainConfig.ssl = true // Auto-provision SSL
      console.log(`Domain ${domain} verified successfully!`)
    } else {
      console.log(`Domain ${domain} verification failed. Please check DNS records.`)
    }

    return isVerified
  }

  // Get domain configuration
  getDomainConfig(domain: string) {
    return this.customDomains.get(domain) || null
  }

  // Get all domains for tenant
  getTenantDomains(tenantId: string): Array<{
    domain: string
    brandId: string
    verified: boolean
    ssl: boolean
  }> {
    const domains = []

    for (const [domain, config] of this.customDomains.entries()) {
      if (config.tenantId === tenantId) {
        domains.push({
          domain,
          brandId: config.brandId,
          verified: config.verified,
          ssl: config.ssl
        })
      }
    }

    return domains
  }

  private isValidDomain(domain: string): boolean {
    const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])*$/;
    return domainRegex.test(domain)
  }

  private generateDNSRecords(domain: string, tenantId: string): {
    cname: string
    txt: { name: string; value: string }
  } {
    const verificationToken = crypto.randomUUID().replace(/-/g, '')

    return {
      cname: `app.example.com`,
      txt: {
        name: `_verification-challenge.${domain}`,
        value: `tenant-${tenantId}-${verificationToken}`
      }
    }
  }
}

// Usage Example
async function whiteLabelExample() {
  console.log('=== White-Label Solution Management ===\n')

  // Initialize services
  const themeService = new ThemeManagementService()
  const brandMiddleware = new BrandMiddleware(themeService)
  const emailService = new WhiteLabelEmailService(themeService)
  const domainService = new CustomDomainService()

  // Create custom brand configuration
  console.log('1. Creating custom brand configuration')

  const techCorpBrand: BrandConfig = {
    tenantId: 'tech-corp-123',
    brandId: 'tech-corp-brand',
    name: 'TechCorp Analytics',
    domain: 'analytics.techcorp.com',
    logo: {
      primary: 'https://assets.techcorp.com/logo-primary.png',
      secondary: 'https://assets.techcorp.com/logo-light.png',
      favicon: 'https://assets.techcorp.com/favicon.ico',
      emailLogo: 'https://assets.techcorp.com/email-logo.png'
    },
    colors: {
      primary: '#2563eb',
      secondary: '#64748b',
      accent: '#10b981',
      background: '#ffffff',
      text: '#1f2937',
      error: '#ef4444',
      success: '#10b981',
      warning: '#f59e0b'
    },
    typography: {
      fontFamily: '"Inter", sans-serif',
      headingFont: '"Inter Display", sans-serif',
      baseFontSize: '16px',
      scale: [0.75, 0.875, 1, 1.125, 1.25, 1.5, 1.875, 2.25, 3]
    },
    layout: {
      headerHeight: '72px',
      sidebarWidth: '280px',
      borderRadius: '8px',
      spacing: {
        small: '12px',
        medium: '20px',
        large: '32px'
      }
    },
    customCSS: `
      .tech-corp-button {
        background: linear-gradient(135deg, #2563eb, #1d4ed8);
        border: none;
        transition: all 0.3s ease;
      }

      .tech-corp-button:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
      }
    `,
    features: {
      showBranding: false,
      allowCustomDomain: true,
      customLogin: true,
      customEmails: true,
      whiteLabelReports: true
    },
    integration: {
      analytics: {
        provider: 'segment',
        trackingId: 'seg_techcorp_123abc'
      },
      support: {
        provider: 'zendesk',
        apiKey: 'zendesk_techcorp_key'
      },
      notifications: {
        emailProvider: 'sendgrid',
        smsProvider: 'twilio',
        templates: themeService['getDefaultEmailTemplates']()
      }
    },
    createdAt: new Date(),
    updatedAt: new Date()
  }

  await themeService.saveBrandConfig(techCorpBrand)

  // Create second brand (agency model)
  console.log('\n2. Creating agency brand (cloned from first)')

  const agencyBrand = themeService.cloneTheme('tech-corp-brand', 'digital-agency-brand', {
    tenantId: 'digital-agency-456',
    name: 'Digital Agency Pro',
    domain: 'pro.digitalagency.io',
    colors: {
      primary: '#7c3aed',
      secondary: '#6b7280',
      accent: '#ec4899',
      background: '#ffffff',
      text: '#111827',
      error: '#dc2626',
      success: '#059669',
      warning: '#d97706'
    },
    logo: {
      primary: 'https://assets.digitalagency.io/logo.png',
      favicon: 'https://assets.digitalagency.io/favicon.ico'
    }
  })

  await themeService.saveBrandConfig(agencyBrand)

  // Generate theme files
  console.log('\n3. Generating theme assets')

  const techCorpCSS = themeService.generateThemeCSS(techCorpBrand)
  const techCorpJS = themeService.generateThemeJS(techCorpBrand)

  console.log(`Generated CSS for ${techCorpBrand.name} (${techCorpCSS.length} chars)`)
  console.log(`Generated JS for ${techCorpBrand.name} (${techCorpJS.length} chars)`)

  // Template rendering example
  console.log('\n4. Template rendering example')

  const htmlTemplate = `
    <!DOCTYPE html>
    <html>
    <head>
      <title>{{BRAND_NAME}} - Dashboard</title>
      <link rel="icon" href="{{FAVICON}}">
      <style>{{BRAND_CSS}}</style>
    </head>
    <body>
      <header class="header">
        <img src="{{BRAND_LOGO}}" alt="{{BRAND_NAME}}" height="32">
      </header>
      <main>
        <h1 class="text-primary">Welcome to {{BRAND_NAME}}</h1>
        <p>Your customized dashboard experience.</p>
      </main>
      <script>{{BRAND_JS}}</script>
    </body>
    </html>
  `

  const brandedHTML = brandMiddleware.generateBrandedHTML(htmlTemplate, techCorpBrand)
  console.log(`Generated branded HTML for ${techCorpBrand.name} (${brandedHTML.length} chars)`)

  // Branded email example
  console.log('\n5. Branded email generation')

  const welcomeEmail = emailService.generateBrandedEmail('tech-corp-brand', 'welcome', {
    brand_name: techCorpBrand.name,
    login_url: 'https://analytics.techcorp.com/login',
    user_name: 'John Doe'
  })

  console.log(`Generated welcome email subject: ${welcomeEmail.subject}`)

  // Send branded email
  await emailService.sendBrandedEmail(
    'tech-corp-brand',
    ['[email protected]'],
    'welcome',
    {
      brand_name: techCorpBrand.name,
      login_url: 'https://analytics.techcorp.com/login',
      user_name: 'John Doe',
      recipient_email: '[email protected]'
    }
  )

  // Custom domain setup
  console.log('\n6. Custom domain configuration')

  await domainService.addCustomDomain(
    'tech-corp-123',
    'tech-corp-brand',
    'analytics.techcorp.com'
  )

  await domainService.addCustomDomain(
    'digital-agency-456',
    'digital-agency-brand',
    'clients.pro.digitalagency.io'
  )

  // Simulate domain verification
  await domainService.verifyCustomDomain('analytics.techcorp.com')

  // Get domain information
  const tenantDomains = domainService.getTenantDomains('tech-corp-123')
  console.log('\n7. Tenant domain status')
  tenantDomains.forEach(domain => {
    console.log(`  ${domain.domain}: ${domain.verified ? 'Verified' : 'Pending'} (SSL: ${domain.ssl})`)
  })

  // Theme analytics
  console.log('\n8. Theme management summary')
  console.log('Total brands configured:', Array.from(themeService['themes'].keys()).length)
  console.log('Custom domains configured:', Array.from(domainService['customDomains'].keys()).length)
}

export {
  BrandConfig,
  ThemeManagementService,
  BrandMiddleware,
  WhiteLabelEmailService,
  CustomDomainService,
  whiteLabelExample
}