import { Pool } from 'pg'
import type { HijackEvent, WebhookSubscription, WebhookDelivery, CreateHijackEventInput, CreateWebhookInput } from './types'

export class HijackAlertsDatabaseClient {
  constructor(private pool: Pool) {}

  async insertHijackEvent(input: CreateHijackEventInput): Promise<HijackEvent> {
    const result = await this.pool.query(
      `INSERT INTO hijack_events (asn, prefix, expected_asn, detected_asns, hijack_type, severity, description, details)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [input.asn, input.prefix, input.expected_asn, input.detected_asns, input.hijack_type, input.severity, input.description, JSON.stringify(input.details)]
    )
    return this.mapRow(result.rows[0])
  }

  async getRecentHijackEvent(asn: number, prefix: string, hoursBack: number = 6): Promise<HijackEvent | null> {
    const result = await this.pool.query(
      `SELECT * FROM hijack_events
       WHERE asn = $1 AND prefix = $2 AND detected_at > NOW() - INTERVAL '${hoursBack} hours'
       ORDER BY detected_at DESC LIMIT 1`,
      [asn, prefix]
    )
    return result.rows.length > 0 ? this.mapRow(result.rows[0]) : null
  }

  async getHijacksByAsn(asn: number, limit: number = 50, offset: number = 0, resolved?: boolean): Promise<{ total: number; events: HijackEvent[] }> {
    let query = 'SELECT * FROM hijack_events WHERE asn = $1'
    const params: unknown[] = [asn]

    if (resolved !== undefined) {
      query += ` AND resolved = $${params.length + 1}`
      params.push(resolved)
    }

    query += ' ORDER BY detected_at DESC LIMIT $' + (params.length + 1) + ' OFFSET $' + (params.length + 2)
    params.push(limit, offset)

    const [eventsResult, countResult] = await Promise.all([
      this.pool.query(query, params),
      this.pool.query('SELECT COUNT(*) as total FROM hijack_events WHERE asn = $1', [asn])
    ])

    return {
      total: parseInt(countResult.rows[0].total),
      events: eventsResult.rows.map((row) => this.mapRow(row))
    }
  }

  async resolveHijack(event_id: number, resolution_notes: string): Promise<HijackEvent> {
    const result = await this.pool.query(
      `UPDATE hijack_events SET resolved = true, resolved_at = NOW()
       WHERE id = $1
       RETURNING *`,
      [event_id]
    )
    return this.mapRow(result.rows[0])
  }

  async createWebhookSubscription(input: CreateWebhookInput, secretKey: string): Promise<WebhookSubscription> {
    const result = await this.pool.query(
      `INSERT INTO webhook_subscriptions (asn, endpoint_url, secret_key, timeout_ms, max_retries)
       VALUES ($1, $2, $3, $4, $5)
       RETURNING *`,
      [input.asn, input.endpoint_url, secretKey, input.timeout_ms || 10000, input.max_retries || 3]
    )
    return this.mapWebhookRow(result.rows[0])
  }

  async getWebhooksByAsn(asn: number): Promise<WebhookSubscription[]> {
    const result = await this.pool.query('SELECT * FROM webhook_subscriptions WHERE asn = $1 AND active = true', [asn])
    return result.rows.map((row) => this.mapWebhookRow(row))
  }

  async getWebhookSubscription(webhook_id: number): Promise<WebhookSubscription | null> {
    const result = await this.pool.query('SELECT * FROM webhook_subscriptions WHERE id = $1', [webhook_id])
    return result.rows.length > 0 ? this.mapWebhookRow(result.rows[0]) : null
  }

  async deleteWebhookSubscription(webhook_id: number): Promise<void> {
    await this.pool.query('DELETE FROM webhook_subscriptions WHERE id = $1', [webhook_id])
  }

  async updateWebhookStatus(webhook_id: number, active: boolean): Promise<WebhookSubscription | null> {
    const result = await this.pool.query('UPDATE webhook_subscriptions SET active = $1 WHERE id = $2 RETURNING *', [active, webhook_id])
    return result.rows.length > 0 ? this.mapWebhookRow(result.rows[0]) : null
  }

  async recordWebhookDelivery(subscription_id: number, event_id: number, attempt: number, status: number | null, body: string | null, error: string | null): Promise<WebhookDelivery> {
    const nextRetryAt = status && status >= 400 ? new Date(Date.now() + Math.pow(2, attempt) * 1000) : null

    const result = await this.pool.query(
      `INSERT INTO webhook_deliveries (subscription_id, event_id, attempt_number, response_status, response_body, error_message, next_retry_at)
       VALUES ($1, $2, $3, $4, $5, $6, $7)
       RETURNING *`,
      [subscription_id, event_id, attempt, status, body, error, nextRetryAt]
    )
    return this.mapDeliveryRow(result.rows[0])
  }

  async getFailedDeliveriesForRetry(): Promise<Array<{ webhook: WebhookSubscription; event: HijackEvent; delivery: WebhookDelivery }>> {
    const result = await this.pool.query(
      `SELECT 
         ws.id as ws_id, ws.asn as ws_asn, ws.endpoint_url, ws.secret_key, ws.created_at as ws_created_at,
         ws.last_triggered_at, ws.failure_count, ws.active, ws.max_retries, ws.timeout_ms, ws.metadata,
         he.id as he_id, he.asn as he_asn, he.prefix, he.detected_at, he.expected_asn, he.detected_asns,
         he.hijack_type, he.severity, he.description, he.details, he.resolved, he.resolved_at, he.created_at as he_created_at,
         wd.id as wd_id, wd.subscription_id as wd_subscription_id, wd.event_id as wd_event_id, wd.attempt_number as wd_attempt_number,
         wd.sent_at as wd_sent_at, wd.response_status as wd_response_status, wd.response_body as wd_response_body,
         wd.error_message as wd_error_message, wd.next_retry_at as wd_next_retry_at
       FROM webhook_deliveries wd
       JOIN webhook_subscriptions ws ON wd.subscription_id = ws.id
       JOIN hijack_events he ON wd.event_id = he.id
       WHERE wd.next_retry_at <= NOW() AND wd.attempt_number < ws.max_retries AND ws.active = true
       ORDER BY wd.next_retry_at ASC
       LIMIT 1000`
    )

    return result.rows.map((row) => ({
      webhook: this.mapWebhookRow(row, 'ws_'),
      event: this.mapRow(row, 'he_'),
      delivery: this.mapDeliveryRow(row, 'wd_')
    }))
  }

  async updateWebhookLastTriggered(webhook_id: number, failure_count: number = 0): Promise<void> {
    await this.pool.query('UPDATE webhook_subscriptions SET last_triggered_at = NOW(), failure_count = $1 WHERE id = $2', [failure_count, webhook_id])
  }

  private mapRow(row: Record<string, unknown>, prefix: string = ''): HijackEvent {
    const key = (k: string) => (prefix ? `${prefix}${k}` : k)
    return {
      id: row[key('id')] as number,
      asn: row[key('asn')] as number,
      prefix: row[key('prefix')] as string,
      detected_at: new Date(row[key('detected_at')] as string),
      expected_asn: row[key('expected_asn')] as number,
      detected_asns: row[key('detected_asns')] as number[],
      hijack_type: row[key('hijack_type')] as 'MOAS' | 'HIJACK' | 'LEAK',
      severity: row[key('severity')] as 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW',
      description: row[key('description')] as string,
      details: row[key('details')] as Record<string, unknown>,
      resolved: row[key('resolved')] as boolean,
      resolved_at: row[key('resolved_at')] ? new Date(row[key('resolved_at')] as string) : null,
      created_at: new Date(row[key('created_at')] as string)
    }
  }

  private mapWebhookRow(row: Record<string, unknown>, prefix: string = ''): WebhookSubscription {
    const key = (k: string) => (prefix ? `${prefix}${k}` : k)
    return {
      id: row[key('id')] as number,
      asn: row[key('asn')] as number,
      endpoint_url: row[key('endpoint_url')] as string,
      secret_key: row[key('secret_key')] as string,
      created_at: new Date(row[key('created_at')] as string),
      last_triggered_at: row[key('last_triggered_at')] ? new Date(row[key('last_triggered_at')] as string) : null,
      failure_count: row[key('failure_count')] as number,
      active: row[key('active')] as boolean,
      max_retries: row[key('max_retries')] as number,
      timeout_ms: row[key('timeout_ms')] as number,
      metadata: row[key('metadata')] as Record<string, unknown>
    }
  }

  private mapDeliveryRow(row: Record<string, unknown>, prefix: string = ''): WebhookDelivery {
    const key = (k: string) => (prefix ? `${prefix}${k}` : k)
    return {
      id: row[key('id')] as number,
      subscription_id: row[key('subscription_id')] as number,
      event_id: row[key('event_id')] as number,
      attempt_number: row[key('attempt_number')] as number,
      sent_at: new Date(row[key('sent_at')] as string),
      response_status: row[key('response_status')] as number | null,
      response_body: row[key('response_body')] as string | null,
      error_message: row[key('error_message')] as string | null,
      next_retry_at: row[key('next_retry_at')] ? new Date(row[key('next_retry_at')] as string) : null
    }
  }
}
