import { Context } from '../types/context'
import { AppErrors } from '../lib/errors'

export interface ResourceFilter {
  type?: string
  status?: string
  siteId?: string
  tenantId?: string
}

export interface CreateResourceInput {
  name: string
  type: string
  siteId: string
  metadata?: Record<string, unknown>
}

export interface UpdateResourceInput {
  name?: string
  metadata?: Record<string, unknown>
}

interface ResourceRow {
  id: string
  name: string
  type: string
  status: string
  site_id: string | null
  tenant_id: string | null
  metadata: string | Record<string, unknown> | null
  created_at: Date
  updated_at: Date
  [key: string]: unknown
}

interface SiteRow {
  id: string
  name: string
  region: string
  status: string
  metadata: string | Record<string, unknown> | null
  created_at: Date
  updated_at: Date
  [key: string]: unknown
}

export async function getResources(context: Context, filter?: ResourceFilter) {
  const db = context.db
  // Use LEFT JOIN to fetch resources and sites in a single query (fixes N+1 problem)
  let query = `
    SELECT 
      r.id, r.name, r.type, r.status, r.site_id, r.tenant_id, r.metadata, 
      r.created_at, r.updated_at,
      s.id as site_id_full, s.name as site_name, s.region as site_region, 
      s.status as site_status, s.metadata as site_metadata,
      s.created_at as site_created_at, s.updated_at as site_updated_at
    FROM resources r
    LEFT JOIN sites s ON r.site_id = s.id
    WHERE 1=1
  `
  const params: unknown[] = []
  let paramCount = 1

  // Tenant-aware filtering (superior to Azure)
  if (context.tenantContext) {
    if (context.tenantContext.isSystemAdmin) {
      // System admins can see all resources
    } else if (context.tenantContext.tenantId) {
      // Filter by tenant
      query += ` AND (r.tenant_id = $${paramCount} OR r.tenant_id IS NULL)`
      params.push(context.tenantContext.tenantId)
      paramCount++
    } else {
      // Non-tenant users only see system resources
      query += ` AND r.tenant_id IS NULL`
    }
  } else {
    // Unauthenticated users see nothing
    query += ` AND 1=0`
  }

  if (filter?.type) {
    query += ` AND r.type = $${paramCount}`
    params.push(filter.type)
    paramCount++
  }

  if (filter?.status) {
    query += ` AND r.status = $${paramCount}`
    params.push(filter.status)
    paramCount++
  }

  if (filter?.siteId) {
    query += ` AND r.site_id = $${paramCount}`
    params.push(filter.siteId)
    paramCount++
  }

  query += ' ORDER BY r.created_at DESC'

  const result = await db.query(query, params)
  // Map results using the joined data (no additional queries needed)
  return result.rows.map((row) => mapResourceWithSite(row))
}

export async function getResource(context: Context, id: string) {
  const db = context.db
  let query = 'SELECT * FROM resources WHERE id = $1'
  const params: unknown[] = [id]
  let paramCount = 2

  // Tenant-aware filtering
  if (context.tenantContext) {
    if (context.tenantContext.isSystemAdmin) {
      // System admins can see all resources
    } else if (context.tenantContext.tenantId) {
      query += ` AND (tenant_id = $${paramCount} OR tenant_id IS NULL)`
      params.push(context.tenantContext.tenantId)
      paramCount++
    } else {
      query += ` AND tenant_id IS NULL`
    }
  } else {
    query += ` AND 1=0`
  }

  const result = await db.query(query, params)

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

  return mapResource(result.rows[0], context)
}

export async function createResource(context: Context, input: CreateResourceInput) {
  const db = context.db
  
  // Set tenant_id from context if available
  const tenantId = context.tenantContext?.tenantId || null
  
  // Enforce tenant quotas if tenant context is available
  if (tenantId) {
    const { tenantService } = await import('./tenant.js')
    
    // Calculate resource requirements based on input
    const resourceRequest: {
      compute?: { vcpu?: number; memory?: number; instances?: number }
      storage?: { size?: number }
      network?: { bandwidth?: number }
    } = {}
    
    // Extract compute requirements from metadata or input
    const metadata = input.metadata || {}
    if (metadata.cpu || metadata.vcpu) {
      const cpu = typeof metadata.cpu === 'number' ? metadata.cpu : (typeof metadata.vcpu === 'number' ? metadata.vcpu : 1)
      const memoryStr = typeof metadata.memory === 'string' ? metadata.memory : String(metadata.memory || '0')
      resourceRequest.compute = {
        vcpu: cpu,
        memory: parseFloat(memoryStr.replace(/[^0-9.]/g, '')) || 0,
        instances: 1,
      }
    }
    
    // Extract storage requirements
    if (metadata.storage || metadata.disk) {
      const storageSize = typeof metadata.storage === 'string' 
        ? metadata.storage 
        : (typeof metadata.disk === 'string' ? metadata.disk : String(metadata.storage || metadata.disk || '0'))
      resourceRequest.storage = {
        size: parseFloat(storageSize.replace(/[^0-9.]/g, '')) || 0,
      }
    }
    
    // Enforce quota - will throw error if quota exceeded
    await tenantService.enforceQuota(tenantId, resourceRequest)
  }
  
  const result = await db.query(
    `INSERT INTO resources (name, type, status, site_id, tenant_id, metadata)
     VALUES ($1, $2, $3, $4, $5, $6)
     RETURNING *`,
    [input.name, input.type, 'PENDING', input.siteId, tenantId, JSON.stringify(input.metadata || {})]
  )

  const resource = await mapResource(result.rows[0], context)
  
  // Record initial usage for billing (per-second granularity)
  if (tenantId) {
    try {
      const { billingService } = await import('./billing.js')
      const metadata = input.metadata || {}
      
      // Calculate initial cost based on resource type and specs
      let initialCost = 0
      if (input.type === 'VM' || input.type === 'CONTAINER') {
        const cpu = typeof metadata.cpu === 'number' ? metadata.cpu : (typeof metadata.vcpu === 'number' ? metadata.vcpu : 1)
        const memory = typeof metadata.memory === 'string' 
          ? parseFloat(metadata.memory.replace(/[^0-9.]/g, '')) 
          : (typeof metadata.memory === 'number' ? metadata.memory : 0)
        // Simplified pricing: $0.01 per vCPU-hour, $0.005 per GB-hour
        initialCost = (cpu * 0.01 + memory * 0.005) / 3600 // Per second
      } else if (input.type === 'STORAGE') {
        const storageSize = typeof metadata.storage === 'string' 
          ? parseFloat(metadata.storage.replace(/[^0-9.]/g, '')) 
          : (typeof metadata.storage === 'number' ? metadata.storage : 0)
        // $0.0001 per GB-hour = per second
        initialCost = (storageSize * 0.0001) / 3600
      }
      
      if (initialCost > 0) {
        await billingService.recordUsage({
          tenantId,
          resourceId: resource.id,
          resourceType: input.type,
          metricType: 'PROVISIONING',
          quantity: 1,
          unit: 'instance',
          cost: initialCost,
          currency: 'USD',
          timestamp: new Date(),
          labels: { action: 'create', resourceType: input.type },
        })
      }
    } catch (error) {
      // Log but don't fail resource creation if billing recording fails
      const { logger } = await import('../lib/logger.js')
      logger.warn('Failed to record usage for resource creation', { error, resourceId: resource.id })
    }
  }
  
  // Record on blockchain if configured (future implementation)
  // This would integrate with blockchain smart contracts for immutable resource tracking
  if (process.env.BLOCKCHAIN_ENABLED === 'true') {
    try {
      const { blockchainService } = await import('./blockchain.js')
      await blockchainService.initialize()
      // Future: Record resource provisioning on blockchain
      // await blockchainService.recordResourceProvisioning(...)
    } catch (error) {
      // Log but don't fail if blockchain is not configured
      const { logger } = await import('../lib/logger.js')
      logger.warn('Failed to record resource on blockchain', { error, resourceId: resource.id })
    }
  }
  
  // Publish subscription event
  const { publishResourceCreated } = await import('../schema/subscriptions')
  publishResourceCreated(resource)

  return resource
}

export async function updateResource(context: Context, id: string, input: UpdateResourceInput) {
  const db = context.db
  const updates: string[] = []
  const params: unknown[] = []
  let paramCount = 1

  if (input.name !== undefined) {
    updates.push(`name = $${paramCount}`)
    params.push(input.name)
    paramCount++
  }

  if (input.metadata !== undefined) {
    updates.push(`metadata = $${paramCount}`)
    params.push(JSON.stringify(input.metadata))
    paramCount++
  }

  if (updates.length === 0) {
    return getResource(context, id)
  }

  params.push(id)
  const result = await db.query(
    `UPDATE resources SET ${updates.join(', ')} WHERE id = $${paramCount} RETURNING *`,
    params
  )

  const resource = await mapResource(result.rows[0], context)
  
  // Publish subscription event
  const { publishResourceUpdated } = await import('../schema/subscriptions')
  publishResourceUpdated(id, resource)

  return resource
}

export async function deleteResource(context: Context, id: string) {
  const db = context.db
  await db.query('DELETE FROM resources WHERE id = $1', [id])
  
  // Publish subscription event
  const { publishResourceDeleted } = await import('../schema/subscriptions')
  publishResourceDeleted(id)
  
  return true
}

// Optimized mapping function for joined queries (no additional DB queries)
function mapResourceWithSite(row: {
  id: string
  name: string
  type: string
  status: string
  site_id: string | null
  tenant_id: string | null
  metadata: string | Record<string, unknown> | null
  created_at: Date
  updated_at: Date
  site_id_full: string | null
  site_name: string | null
  site_region: string | null
  site_status: string | null
  site_metadata: string | Record<string, unknown> | null
  site_created_at: Date | null
  site_updated_at: Date | null
}) {
  const metadata = typeof row.metadata === 'string' 
    ? (JSON.parse(row.metadata) as Record<string, unknown>)
    : ((row.metadata as Record<string, unknown>) || {})

  // Map site from joined data (if available)
  let site = null
  if (row.site_id_full) {
    const siteMetadata = typeof row.site_metadata === 'string'
      ? (JSON.parse(row.site_metadata) as Record<string, unknown>)
      : ((row.site_metadata as Record<string, unknown>) || {})
    
    site = {
      id: row.site_id_full,
      name: row.site_name || 'Unknown',
      region: row.site_region || '',
      status: row.site_status || 'INACTIVE',
      metadata: siteMetadata,
      createdAt: row.site_created_at || new Date(),
      updatedAt: row.site_updated_at || new Date(),
    }
  } else if (row.site_id) {
    // Site ID exists but site not found (LEFT JOIN returned null)
    site = {
      id: row.site_id,
      name: 'Unknown',
      region: '',
      status: 'INACTIVE',
      metadata: {},
      createdAt: new Date(),
      updatedAt: new Date(),
    }
  }

  return {
    id: row.id,
    name: row.name,
    type: row.type,
    status: row.status,
    site: site || { id: row.site_id || '', name: 'Unknown', region: '', status: 'INACTIVE' },
    metadata,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

async function mapResource(row: ResourceRow, context: Context) {
  // Get site information
  const siteResult = await context.db.query('SELECT * FROM sites WHERE id = $1', [row.site_id])
  const site = siteResult.rows.length > 0 ? mapSite(siteResult.rows[0] as SiteRow) : null

  const metadata = typeof row.metadata === 'string' 
    ? (JSON.parse(row.metadata) as Record<string, unknown>)
    : ((row.metadata as Record<string, unknown>) || {})

  return {
    id: row.id,
    name: row.name,
    type: row.type,
    status: row.status,
    site: site || { id: row.site_id || '', name: 'Unknown', region: '', status: 'INACTIVE' },
    metadata,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

function mapSite(row: SiteRow) {
  const metadata = typeof row.metadata === 'string' 
    ? (JSON.parse(row.metadata) as Record<string, unknown>)
    : ((row.metadata as Record<string, unknown>) || {})

  return {
    id: row.id,
    name: row.name,
    region: row.region,
    status: row.status,
    metadata,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

