import { Context } from '../types/context'
import { logger } from '../lib/logger'

export interface PolicyFinding {
  type: string
  message: string
  remediation?: string
}

export interface Policy {
  id: string
  name: string
  description?: string
  policyType: string
  enabled: boolean
  severity: string
  rule: Record<string, unknown>
  scope: Record<string, unknown>
  metadata: Record<string, unknown>
  createdAt: Date
  updatedAt: Date
}

export interface PolicyEvaluation {
  id: string
  policyId: string
  resourceId: string
  status: string
  findings: PolicyFinding[]
  evaluatedAt: Date
}

interface PolicyRule {
  type: string
  requiredTags?: string[]
  dataResidency?: string
  requireEncryption?: boolean
  maxIdleTime?: number
  [key: string]: unknown
}

interface ResourceRow {
  id: string
  tags: string[] | null
  region: string | null
  metadata: Record<string, unknown> | null
  [key: string]: unknown
}

interface PolicyRow {
  id: string
  name: string
  description: string | null
  policy_type: string
  enabled: boolean
  severity: string
  rule: Record<string, unknown> | null
  scope: Record<string, unknown> | null
  metadata: Record<string, unknown> | null
  created_at: Date
  updated_at: Date
  [key: string]: unknown
}

interface PolicyEvaluationRow {
  id: string
  policy_id: string
  resource_id: string
  status: string
  findings: PolicyFinding[] | null
  evaluated_at: Date
  [key: string]: unknown
}

interface PolicyViolationRow {
  id: string
  policy_id: string
  resource_id: string
  severity: string
  message: string
  remediation: string | null
  status: string
  created_at: Date
  resolved_at: Date | null
  resolved_by: string | null
  [key: string]: unknown
}

export interface PolicyViolation {
  id: string
  policyId: string
  resourceId: string
  severity: string
  message: string
  remediation?: string
  status: string
  createdAt: Date
  resolvedAt?: Date
  resolvedBy?: string
}

export interface CreatePolicyInput {
  name: string
  description?: string
  policyType: string
  enabled?: boolean
  severity?: string
  rule: Record<string, unknown>
  scope?: Record<string, unknown>
  metadata?: Record<string, unknown>
}

export interface UpdatePolicyInput {
  name?: string
  description?: string
  enabled?: boolean
  severity?: string
  rule?: Record<string, unknown>
  scope?: Record<string, unknown>
  metadata?: Record<string, unknown>
}

export async function getPolicies(
  context: Context,
  filter?: { policyType?: string; enabled?: boolean }
): Promise<Policy[]> {
  const db = context.db
  let query = 'SELECT * FROM policies WHERE 1=1'
  const params: unknown[] = []
  let paramCount = 1

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

  if (filter?.enabled !== undefined) {
    query += ` AND enabled = $${paramCount}`
    params.push(filter.enabled)
    paramCount++
  }

  query += ' ORDER BY created_at DESC'

  const result = await db.query(query, params)
  return result.rows.map(mapPolicy)
}

export async function getPolicy(context: Context, id: string): Promise<Policy> {
  const db = context.db
  const result = await db.query('SELECT * FROM policies WHERE id = $1', [id])

  if (result.rows.length === 0) {
    throw new Error('Policy not found')
  }

  return mapPolicy(result.rows[0])
}

export async function createPolicy(
  context: Context,
  input: CreatePolicyInput
): Promise<Policy> {
  const db = context.db
  const result = await db.query(
    `INSERT INTO policies (
      name, description, policy_type, enabled, severity, rule, scope, metadata
    ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
    RETURNING *`,
    [
      input.name,
      input.description || null,
      input.policyType,
      input.enabled !== undefined ? input.enabled : true,
      input.severity || 'MEDIUM',
      JSON.stringify(input.rule),
      JSON.stringify(input.scope || {}),
      JSON.stringify(input.metadata || {}),
    ]
  )

  return mapPolicy(result.rows[0])
}

export async function updatePolicy(
  context: Context,
  id: string,
  input: UpdatePolicyInput
): Promise<Policy> {
  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.description !== undefined) {
    updates.push(`description = $${paramCount}`)
    params.push(input.description)
    paramCount++
  }

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

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

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

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

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

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

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

  return mapPolicy(result.rows[0])
}

export async function deletePolicy(context: Context, id: string): Promise<boolean> {
  const db = context.db
  await db.query('DELETE FROM policies WHERE id = $1', [id])
  return true
}

export async function evaluatePolicy(
  context: Context,
  policyId: string,
  resourceId: string
): Promise<PolicyEvaluation> {
  const db = context.db

  // Get policy
  const policy = await getPolicy(context, policyId)

  // Get resource
  const resourceResult = await db.query(
    'SELECT * FROM resource_inventory WHERE id = $1',
    [resourceId]
  )

  if (resourceResult.rows.length === 0) {
    throw new Error('Resource not found')
  }

  const resource = resourceResult.rows[0]

  // Evaluate policy rule
  const findings = evaluateRule(policy.rule as PolicyRule, resource as ResourceRow)
  const isCompliant = findings.length === 0
  const status = isCompliant ? 'COMPLIANT' : 'NON_COMPLIANT'

  // Upsert evaluation
  const evalResult = await db.query(
    `INSERT INTO policy_evaluations (policy_id, resource_id, status, findings)
     VALUES ($1, $2, $3, $4)
     ON CONFLICT (policy_id, resource_id)
     DO UPDATE SET status = $3, findings = $4, evaluated_at = NOW()
     RETURNING *`,
    [policyId, resourceId, status, JSON.stringify(findings)]
  )

  // Create violations for non-compliant resources
  if (!isCompliant) {
    for (const finding of findings) {
      await db.query(
        `INSERT INTO policy_violations (
          policy_id, resource_id, severity, message, remediation, status
        ) VALUES ($1, $2, $3, $4, $5, 'OPEN')
        ON CONFLICT DO NOTHING`,
        [
          policyId,
          resourceId,
          policy.severity,
          finding.message || 'Policy violation detected',
          finding.remediation || null,
        ]
      )
    }
  } else {
    // Resolve existing violations
    await db.query(
      `UPDATE policy_violations
       SET status = 'RESOLVED', resolved_at = NOW()
       WHERE policy_id = $1 AND resource_id = $2 AND status = 'OPEN'`,
      [policyId, resourceId]
    )
  }

  return mapPolicyEvaluation(evalResult.rows[0])
}

export async function evaluateAllPolicies(
  context: Context,
  resourceId?: string
): Promise<number> {
  const db = context.db

  // Get all enabled policies
  const policies = await getPolicies(context, { enabled: true })

  let evaluated = 0

  if (resourceId) {
    // Evaluate all policies for a specific resource
    for (const policy of policies) {
      try {
        await evaluatePolicy(context, policy.id, resourceId)
        evaluated++
      } catch (error) {
        logger.error(`Failed to evaluate policy ${policy.id}`, { error, policyId: policy.id })
      }
    }
  } else {
    // Evaluate all policies for all resources
    const resourcesResult = await db.query('SELECT id FROM resource_inventory')
    for (const resource of resourcesResult.rows) {
      for (const policy of policies) {
        try {
          await evaluatePolicy(context, policy.id, resource.id)
          evaluated++
        } catch (error) {
          logger.error(`Failed to evaluate policy ${policy.id} for resource ${resource.id}`, { error, policyId: policy.id, resourceId: resource.id })
        }
      }
    }
  }

  return evaluated
}

export async function getPolicyViolations(
  context: Context,
  filter?: { policyId?: string; resourceId?: string; status?: string; severity?: string }
): Promise<PolicyViolation[]> {
  const db = context.db
  let query = 'SELECT * FROM policy_violations WHERE 1=1'
  const params: unknown[] = []
  let paramCount = 1

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

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

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

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

  query += ' ORDER BY created_at DESC'

  const result = await db.query(query, params)
  return result.rows.map(mapPolicyViolation)
}

export async function resolveViolation(
  context: Context,
  violationId: string
): Promise<boolean> {
  const db = context.db
  const userId = context.user?.id || null

  await db.query(
    `UPDATE policy_violations
     SET status = 'RESOLVED', resolved_at = NOW(), resolved_by = $1
     WHERE id = $2`,
    [userId, violationId]
  )

  return true
}

// Helper function to evaluate policy rules
function evaluateRule(rule: PolicyRule, resource: ResourceRow): PolicyFinding[] {
  const findings: PolicyFinding[] = []

  // Example rule evaluation logic
  // This would be expanded based on rule types

  if (rule.type === 'TAGGING') {
    if (rule.requiredTags) {
      const resourceTags = resource.tags || []
      const missingTags = rule.requiredTags.filter(
        (tag: string) => !resourceTags.includes(tag)
      )
      if (missingTags.length > 0) {
        findings.push({
          type: 'MISSING_TAGS',
          message: `Missing required tags: ${missingTags.join(', ')}`,
          remediation: `Add the following tags: ${missingTags.join(', ')}`,
        })
      }
    }
  }

  if (rule.type === 'COMPLIANCE') {
    if (rule.dataResidency && resource.region !== rule.dataResidency) {
      findings.push({
        type: 'DATA_RESIDENCY_VIOLATION',
        message: `Resource must be in region: ${rule.dataResidency}`,
        remediation: `Move resource to region: ${rule.dataResidency}`,
      })
    }
  }

  if (rule.type === 'SECURITY') {
    if (rule.requireEncryption) {
      const metadata = resource.metadata as Record<string, unknown> | null
      if (!metadata?.encrypted) {
        findings.push({
          type: 'ENCRYPTION_REQUIRED',
          message: 'Resource must be encrypted',
          remediation: 'Enable encryption for this resource',
        })
      }
    }
  }

  if (rule.type === 'COST_OPTIMIZATION') {
    if (rule.maxIdleTime) {
      // Check if resource has been idle for too long
      // This would require additional metadata tracking
    }
  }

  return findings
}

function mapPolicy(row: PolicyRow): Policy {
  return {
    id: row.id,
    name: row.name,
    description: row.description || undefined,
    policyType: row.policy_type,
    enabled: row.enabled,
    severity: row.severity,
    rule: (row.rule as Record<string, unknown>) || {},
    scope: (row.scope as Record<string, unknown>) || {},
    metadata: (row.metadata as Record<string, unknown>) || {},
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

function mapPolicyEvaluation(row: PolicyEvaluationRow): PolicyEvaluation {
  return {
    id: row.id,
    policyId: row.policy_id,
    resourceId: row.resource_id,
    status: row.status,
    findings: (row.findings as PolicyFinding[]) || [],
    evaluatedAt: row.evaluated_at,
  }
}

function mapPolicyViolation(row: PolicyViolationRow): PolicyViolation {
  return {
    id: row.id,
    policyId: row.policy_id,
    resourceId: row.resource_id,
    severity: row.severity,
    message: row.message,
    remediation: row.remediation || undefined,
    status: row.status,
    createdAt: row.created_at,
    resolvedAt: row.resolved_at || undefined,
    resolvedBy: row.resolved_by || undefined,
  }
}

