/**
 * Tenant Service
 * Manages multi-tenant operations - Superior to Azure
 */

import { getDb } from '../db/index.js'
import { logger } from '../lib/logger.js'
import { identityService } from './identity.js'
import { Tenant, TenantQuotaUsage, TenantQuotaMetrics } from '../types/tenant.js'
import { AppErrors } from '../lib/errors.js'

export interface CreateTenantInput {
  name: string
  domain?: string
  tier?: 'FREE' | 'STANDARD' | 'ENTERPRISE' | 'SOVEREIGN'
  metadata?: Record<string, unknown>
  quotaLimits?: {
    compute?: { vcpu?: number; memory?: number; instances?: number }
    storage?: { total?: number; perInstance?: number }
    network?: { bandwidth?: number; egress?: number }
    custom?: Record<string, unknown>
  }
}

export interface UpdateTenantInput {
  name?: string
  domain?: string
  status?: 'ACTIVE' | 'SUSPENDED' | 'DELETED' | 'PENDING_ACTIVATION'
  tier?: 'FREE' | 'STANDARD' | 'ENTERPRISE' | 'SOVEREIGN'
  metadata?: Record<string, unknown>
  quotaLimits?: {
    compute?: { vcpu?: number; memory?: number; instances?: number }
    storage?: { total?: number; perInstance?: number }
    network?: { bandwidth?: number; egress?: number }
    custom?: Record<string, unknown>
  }
}

class TenantService {
  /**
   * Create a new tenant
   */
  async createTenant(input: CreateTenantInput): Promise<Tenant> {
    const db = getDb()

    // Generate billing account ID
    const billingAccountId = `BA-${Date.now()}-${Math.random().toString(36).substring(7)}`

    try {
      // Create tenant
      const result = await db.query(
        `INSERT INTO tenants 
         (name, domain, billing_account_id, status, tier, metadata, quota_limits)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING *`,
        [
          input.name,
          input.domain || null,
          billingAccountId,
          'PENDING_ACTIVATION',
          input.tier || 'STANDARD',
          JSON.stringify(input.metadata || {}),
          JSON.stringify(input.quotaLimits || {}),
        ]
      )

      const tenant = result.rows[0]

      // Create billing account
      await db.query(
        `INSERT INTO billing_accounts 
         (tenant_id, account_name, currency)
         VALUES ($1, $2, $3)`,
        [tenant.id, `${input.name} Billing Account`, 'USD']
      )

      // Create Keycloak realm for tenant (if multi-realm support enabled)
      if (process.env.KEYCLOAK_MULTI_REALM === 'true') {
        try {
          await identityService.createTenantRealm(tenant.id, input.name)
        } catch (error) {
          logger.warn('Failed to create Keycloak realm for tenant', { error, tenantId: tenant.id })
        }
      }

      logger.info('Tenant created', { tenantId: tenant.id, name: input.name })

      return this.formatTenant(tenant)
    } catch (error) {
      logger.error('Failed to create tenant', { error, input })
      throw error
    }
  }

  /**
   * Update tenant
   */
  async updateTenant(tenantId: string, input: UpdateTenantInput): Promise<Tenant> {
    const db = getDb()

    const updates: string[] = []
    const values: unknown[] = []
    let paramIndex = 1

    if (input.name !== undefined) {
      updates.push(`name = $${paramIndex++}`)
      values.push(input.name)
    }
    if (input.domain !== undefined) {
      updates.push(`domain = $${paramIndex++}`)
      values.push(input.domain || null)
    }
    if (input.status !== undefined) {
      updates.push(`status = $${paramIndex++}`)
      values.push(input.status)
    }
    if (input.tier !== undefined) {
      updates.push(`tier = $${paramIndex++}`)
      values.push(input.tier)
    }
    if (input.metadata !== undefined) {
      updates.push(`metadata = $${paramIndex++}`)
      values.push(JSON.stringify(input.metadata))
    }
    if (input.quotaLimits !== undefined) {
      updates.push(`quota_limits = $${paramIndex++}`)
      values.push(JSON.stringify(input.quotaLimits))
    }

    if (updates.length === 0) {
      return this.getTenant(tenantId)
    }

    values.push(tenantId)
    const result = await db.query(
      `UPDATE tenants 
       SET ${updates.join(', ')}, updated_at = NOW()
       WHERE id = $${paramIndex}
       RETURNING *`,
      values
    )

    if (result.rows.length === 0) {
      throw AppErrors.tenantNotFound(tenantId)
    }

    return this.formatTenant(result.rows[0])
  }

  /**
   * Get tenant by ID
   */
  async getTenant(tenantId: string): Promise<Tenant> {
    const db = getDb()
    const result = await db.query('SELECT * FROM tenants WHERE id = $1', [tenantId])

    if (result.rows.length === 0) {
      throw AppErrors.tenantNotFound(tenantId)
    }

    return this.formatTenant(result.rows[0])
  }

  /**
   * Get tenant by domain
   */
  async getTenantByDomain(domain: string): Promise<Tenant> {
    const db = getDb()
    const result = await db.query('SELECT * FROM tenants WHERE domain = $1', [domain])

    if (result.rows.length === 0) {
      throw AppErrors.tenantNotFound(domain)
    }

    return this.formatTenant(result.rows[0])
  }

  /**
   * List all tenants (admin only)
   */
  async listTenants(): Promise<Tenant[]> {
    const db = getDb()
    const result = await db.query('SELECT * FROM tenants ORDER BY created_at DESC')
    return result.rows.map((row) => this.formatTenant(row))
  }

  /**
   * Get tenant users
   */
  async getTenantUsers(tenantId: string): Promise<Array<{ id: string; email: string; name: string; role: string }>> {
    return identityService.getTenantUsers(tenantId)
  }

  /**
   * Add user to tenant
   */
  async addUserToTenant(
    tenantId: string,
    userId: string,
    role: string,
    permissions?: Record<string, any>
  ): Promise<void> {
    await identityService.addUserToTenant(tenantId, userId, role, permissions)
  }

  /**
   * Remove user from tenant
   */
  async removeUserFromTenant(tenantId: string, userId: string): Promise<void> {
    await identityService.removeUserFromTenant(tenantId, userId)
  }

  /**
   * Suspend tenant
   */
  async suspendTenant(tenantId: string, reason: string): Promise<void> {
    const db = getDb()
    await db.query(
      `UPDATE tenants 
       SET status = 'SUSPENDED', metadata = jsonb_set(COALESCE(metadata, '{}'), '{suspension_reason}', $2::jsonb), updated_at = NOW()
       WHERE id = $1`,
      [tenantId, JSON.stringify(reason)]
    )
  }

  /**
   * Activate tenant
   */
  async activateTenant(tenantId: string): Promise<void> {
    const db = getDb()
    await db.query(
      `UPDATE tenants 
       SET status = 'ACTIVE', updated_at = NOW()
       WHERE id = $1`,
      [tenantId]
    )
  }

  /**
   * Delete tenant
   */
  async deleteTenant(tenantId: string): Promise<void> {
    const db = getDb()
    await db.query(`UPDATE tenants SET status = 'DELETED', updated_at = NOW() WHERE id = $1`, [
      tenantId,
    ])
  }

  /**
   * Format tenant for GraphQL response
   */
  private formatTenant(row: {
    id: string
    name: string
    domain: string | null
    billing_account_id: string
    status: string
    tier: string
    quota_limits: Record<string, unknown> | null
    metadata: Record<string, unknown> | null
    created_at: Date
    updated_at: Date
  }): Tenant {
    return {
      id: row.id,
      name: row.name,
      domain: row.domain,
      billingAccountId: row.billing_account_id,
      status: row.status,
      tier: row.tier,
      quotaLimits: row.quota_limits || {},
      metadata: row.metadata || {},
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    }
  }

  /**
   * Get current quota usage for a tenant
   */
  async getQuotaUsage(tenantId: string): Promise<TenantQuotaUsage> {
    const db = getDb()

    // Get compute usage (from resource_inventory)
    const computeResult = await db.query(
      `SELECT 
         COUNT(*) as instances,
         COALESCE(SUM((metadata->>'vcpu')::numeric), 0) as vcpu,
         COALESCE(SUM((metadata->>'memory')::numeric), 0) as memory
       FROM resource_inventory
       WHERE tenant_id = $1 
         AND provider_type IN ('PROXMOX', 'KUBERNETES')
         AND status = 'ACTIVE'`,
      [tenantId]
    )

    const compute = computeResult.rows[0] || { instances: 0, vcpu: 0, memory: 0 }

    // Get storage usage
    const storageResult = await db.query(
      `SELECT 
         COALESCE(SUM((metadata->>'size')::numeric), 0) as total,
         COALESCE(MAX((metadata->>'size')::numeric), 0) as per_instance
       FROM resource_inventory
       WHERE tenant_id = $1 
         AND provider_type IN ('PROXMOX', 'KUBERNETES')
         AND resource_type = 'STORAGE'
         AND status = 'ACTIVE'`,
      [tenantId]
    )

    const storage = storageResult.rows[0] || { total: 0, per_instance: 0 }

    // Get network usage (from usage_records)
    const networkResult = await db.query(
      `SELECT 
         COALESCE(SUM(CASE WHEN metric_type = 'BANDWIDTH' THEN quantity ELSE 0 END), 0) as bandwidth,
         COALESCE(SUM(CASE WHEN metric_type = 'EGRESS' THEN quantity ELSE 0 END), 0) as egress
       FROM usage_records
       WHERE tenant_id = $1 
         AND timestamp >= NOW() - INTERVAL '30 days'`,
      [tenantId]
    )

    const network = networkResult.rows[0] || { bandwidth: 0, egress: 0 }

    return {
      compute: {
        instances: parseInt(compute.instances, 10),
        vcpu: parseFloat(compute.vcpu) || 0,
        memory: parseFloat(compute.memory) || 0,
      },
      storage: {
        total: parseFloat(storage.total) || 0,
        perInstance: parseFloat(storage.per_instance) || 0,
      },
      network: {
        bandwidth: parseFloat(network.bandwidth) || 0,
        egress: parseFloat(network.egress) || 0,
      },
    } as TenantQuotaUsage
  }

  /**
   * Check if a quota would be exceeded by a resource request
   */
  async checkQuota(
    tenantId: string,
    resourceRequest: {
      compute?: { vcpu?: number; memory?: number; instances?: number }
      storage?: { size?: number }
      network?: { bandwidth?: number }
    }
  ): Promise<{ allowed: boolean; exceeded: string[]; current: TenantQuotaUsage; limits: TenantQuotas }> {
    const db = getDb()

    // Get tenant quota limits
    const tenantResult = await db.query(`SELECT quota_limits FROM tenants WHERE id = $1`, [tenantId])
    if (tenantResult.rows.length === 0) {
      throw AppErrors.tenantNotFound(tenantId)
    }

    const limits = tenantResult.rows[0].quota_limits || {}
    const current = await this.getQuotaUsage(tenantId)

    const exceeded: string[] = []

    // Check compute quotas
    if (resourceRequest.compute) {
      if (resourceRequest.compute.instances !== undefined) {
        const newInstances = current.compute.instances + resourceRequest.compute.instances
        if (limits.compute?.instances && newInstances > limits.compute.instances) {
          exceeded.push(
            `compute.instances: ${newInstances} exceeds limit of ${limits.compute.instances}`
          )
        }
      }

      if (resourceRequest.compute.vcpu !== undefined) {
        const newVcpu = current.compute.vcpu + resourceRequest.compute.vcpu
        if (limits.compute?.vcpu && newVcpu > limits.compute.vcpu) {
          exceeded.push(`compute.vcpu: ${newVcpu} exceeds limit of ${limits.compute.vcpu}`)
        }
      }

      if (resourceRequest.compute.memory !== undefined) {
        const newMemory = current.compute.memory + resourceRequest.compute.memory
        if (limits.compute?.memory && newMemory > limits.compute.memory) {
          exceeded.push(
            `compute.memory: ${newMemory}GB exceeds limit of ${limits.compute.memory}GB`
          )
        }
      }
    }

    // Check storage quotas
    if (resourceRequest.storage) {
      if (resourceRequest.storage.size !== undefined) {
        const newStorage = current.storage.total + resourceRequest.storage.size
        if (limits.storage?.total && newStorage > limits.storage.total) {
          exceeded.push(`storage.total: ${newStorage}GB exceeds limit of ${limits.storage.total}GB`)
        }

        if (limits.storage?.perInstance && resourceRequest.storage.size > limits.storage.perInstance) {
          exceeded.push(
            `storage.perInstance: ${resourceRequest.storage.size}GB exceeds limit of ${limits.storage.perInstance}GB`
          )
        }
      }
    }

    // Check network quotas
    if (resourceRequest.network) {
      if (resourceRequest.network.bandwidth !== undefined) {
        const newBandwidth = current.network.bandwidth + resourceRequest.network.bandwidth
        if (limits.network?.bandwidth && newBandwidth > limits.network.bandwidth) {
          exceeded.push(
            `network.bandwidth: ${newBandwidth}Mbps exceeds limit of ${limits.network.bandwidth}Mbps`
          )
        }
      }
    }

    return {
      allowed: exceeded.length === 0,
      exceeded,
      current,
      limits: limits as TenantQuotas,
    }
  }

  /**
   * Enforce quota - throw error if quota would be exceeded
   */
  async enforceQuota(
    tenantId: string,
    resourceRequest: {
      compute?: { vcpu?: number; memory?: number; instances?: number }
      storage?: { size?: number }
      network?: { bandwidth?: number }
    }
  ): Promise<void> {
    const check = await this.checkQuota(tenantId, resourceRequest)

    if (!check.allowed) {
      throw AppErrors.quotaExceeded(`Quota exceeded: ${check.exceeded.join(', ')}`, { exceeded: check.exceeded })
    }
  }

  /**
   * Share resource across multiple tenants
   */
  async shareResourceAcrossTenants(
    resourceId: string,
    sourceTenantId: string,
    targetTenants: string[],
    permissions?: Record<string, unknown>
  ): Promise<void> {
    const db = getDb()

    // Verify resource belongs to source tenant
    const resourceResult = await db.query(
      `SELECT tenant_id FROM resource_inventory WHERE id = $1`,
      [resourceId]
    )

    if (resourceResult.rows.length === 0) {
      throw AppErrors.notFound('Resource', resourceId)
    }

    if (resourceResult.rows[0].tenant_id !== sourceTenantId) {
      throw AppErrors.forbidden('Resource does not belong to source tenant')
    }

    // Create resource sharing records
    for (const targetTenantId of targetTenants) {
      // Verify target tenant exists
      const tenantResult = await db.query(`SELECT id FROM tenants WHERE id = $1`, [targetTenantId])
      if (tenantResult.rows.length === 0) {
        throw AppErrors.tenantNotFound(targetTenantId)
      }

      // Create sharing record
      await db.query(
        `INSERT INTO resource_shares 
         (resource_id, source_tenant_id, target_tenant_id, permissions, created_at)
         VALUES ($1, $2, $3, $4, NOW())
         ON CONFLICT (resource_id, target_tenant_id) DO UPDATE
         SET permissions = $4, updated_at = NOW()`,
        [resourceId, sourceTenantId, targetTenantId, JSON.stringify(permissions || {})]
      )

      logger.info('Resource shared across tenants', {
        resourceId,
        sourceTenantId,
        targetTenantId,
      })
    }
  }

  /**
   * Get quota usage metrics for a tenant
   */
  async getQuotaMetrics(tenantId: string): Promise<TenantQuotaMetrics> {
    const db = getDb()

    const tenantResult = await db.query(`SELECT quota_limits FROM tenants WHERE id = $1`, [tenantId])
    if (tenantResult.rows.length === 0) {
      throw AppErrors.tenantNotFound(tenantId)
    }

    const limits = tenantResult.rows[0].quota_limits || {}
    const current = await this.getQuotaUsage(tenantId)

    return {
      compute: {
        used: current.compute,
        limit: limits.compute || {},
        percentage: {
          instances: limits.compute?.instances
            ? (current.compute.instances / limits.compute.instances) * 100
            : 0,
          vcpu: limits.compute?.vcpu ? (current.compute.vcpu / limits.compute.vcpu) * 100 : 0,
          memory: limits.compute?.memory
            ? (current.compute.memory / limits.compute.memory) * 100
            : 0,
        },
      },
      storage: {
        used: current.storage,
        limit: limits.storage || {},
        percentage: {
          total: limits.storage?.total ? (current.storage.total / limits.storage.total) * 100 : 0,
        },
      },
      network: {
        used: current.network,
        limit: limits.network || {},
        percentage: {
          bandwidth: limits.network?.bandwidth
            ? (current.network.bandwidth / limits.network.bandwidth) * 100
            : 0,
        },
      },
    }
  }
}

export const tenantService = new TenantService()

