import type { Ref } from 'vue'
import type {
  AIUIRequest,
  ArchyResponse,
  AIContextCategory,
  ArchyContextResponse,
  ArchyActionResponse,
} from '@/types/aiui-protocol'
import { useAIPermissionsStore } from '@/stores/aiPermissions'
import { useAppStore } from '@/stores/app'
import { useContainerStore, BUNDLED_APPS } from '@/stores/container'
import { rpcClient } from '@/api/rpc-client'
import { fileBrowserClient } from '@/api/filebrowser-client'

/**
 * Context Broker — mediates all communication between AIUI (iframe) and Archy.
 *
 * AIUI sends context/action requests via postMessage.
 * The broker checks permissions, fetches data from Pinia stores,
 * sanitizes it (strips sensitive fields), and responds.
 */
export class ContextBroker {
  private iframe: Ref<HTMLIFrameElement | null>
  private allowedOrigin: string
  private listener: ((e: MessageEvent) => void) | null = null

  constructor(iframe: Ref<HTMLIFrameElement | null>, aiuiUrl: string) {
    this.iframe = iframe
    try {
      const url = new URL(aiuiUrl, window.location.origin)
      this.allowedOrigin = url.origin
    } catch {
      this.allowedOrigin = window.location.origin
    }
  }

  start() {
    this.listener = (e: MessageEvent) => this.handleMessage(e)
    window.addEventListener('message', this.listener)
  }

  stop() {
    if (this.listener) {
      window.removeEventListener('message', this.listener)
      this.listener = null
    }
  }

  sendPermissionsUpdate() {
    const perms = useAIPermissionsStore()
    this.postToIframe({
      type: 'permissions:update',
      categories: perms.enabledCategories,
    })
  }

  sendTheme() {
    this.postToIframe({
      type: 'theme:response',
      theme: { accent: '#fb923c', mode: 'dark' },
    })
  }

  private handleMessage(event: MessageEvent) {
    if (event.origin !== this.allowedOrigin) return

    const msg = event.data as AIUIRequest
    if (!msg || typeof msg.type !== 'string') return

    switch (msg.type) {
      case 'ready':
        this.sendPermissionsUpdate()
        this.sendTheme()
        break
      case 'context:request':
        this.handleContextRequest(msg.id, msg.category, msg.query)
        break
      case 'action:request':
        this.handleActionRequest(msg.id, msg.action, msg.params)
        break
      case 'theme:request':
        this.sendTheme()
        break
    }
  }

  private async handleContextRequest(id: string, category: AIContextCategory, query?: string) {
    const perms = useAIPermissionsStore()

    if (!perms.isEnabled(category)) {
      this.postToIframe({
        type: 'context:response',
        id,
        data: null,
        permitted: false,
      } satisfies ArchyContextResponse)
      return
    }

    const data = await this.fetchAndSanitize(category, query)
    this.postToIframe({
      type: 'context:response',
      id,
      data,
      permitted: true,
    } satisfies ArchyContextResponse)
  }

  private handleActionRequest(id: string, action: string, params: Record<string, string>) {
    const appStore = useAppStore()
    let success = false
    let error: string | undefined

    try {
      switch (action) {
        case 'navigate':
          if (params.path) {
            window.dispatchEvent(new CustomEvent('aiui:navigate', { detail: params.path }))
            success = true
          } else {
            error = 'Missing path parameter'
          }
          break

        case 'open-app':
        case 'launch-app':
          if (params.appId) {
            const url = this.getAppUrl(params.appId)
            if (url) {
              window.dispatchEvent(new CustomEvent('aiui:open-app', { detail: params.appId }))
              success = true
            } else {
              error = `App "${params.appId}" not found or not running`
            }
          } else {
            error = 'Missing appId parameter'
          }
          break

        case 'install-app':
          if (params.appId && params.marketplaceUrl && params.version) {
            const packages = appStore.packages || {}
            const existing = packages[params.appId]
            if (existing && existing.state === 'installed') {
              this.postToIframe({
                type: 'action:response',
                id,
                success: false,
                error: `${params.appId} is already installed`,
              } satisfies ArchyActionResponse)
              return
            }
            // Capture values for use in closure
            const appId = params.appId
            const marketplaceUrl = params.marketplaceUrl
            const version = params.version
            // Emit event for UI confirmation instead of installing directly
            window.dispatchEvent(new CustomEvent('aiui:install-request', {
              detail: { requestId: id, appId, marketplaceUrl, version },
            }))
            {
              const broker = this
              const responseHandler = (e: Event) => {
                const detail = (e as CustomEvent).detail as { requestId: string; confirmed: boolean }
                if (detail.requestId !== id) return
                window.removeEventListener('aiui:install-response', responseHandler)
                if (detail.confirmed) {
                  appStore.installPackage(appId, marketplaceUrl, version).then(() => {
                    broker.postToIframe({
                      type: 'action:response',
                      id,
                      success: true,
                    } satisfies ArchyActionResponse)
                  }).catch((err: Error) => {
                    broker.postToIframe({
                      type: 'action:response',
                      id,
                      success: false,
                      error: err.message,
                    } satisfies ArchyActionResponse)
                  })
                } else {
                  broker.postToIframe({
                    type: 'action:response',
                    id,
                    success: false,
                    error: 'User declined the installation',
                  } satisfies ArchyActionResponse)
                }
              }
              window.addEventListener('aiui:install-response', responseHandler)
              setTimeout(() => {
                window.removeEventListener('aiui:install-response', responseHandler)
              }, 60000)
            }
            return
          }
          error = 'Missing required parameters (appId, marketplaceUrl, version)'
          break

        case 'search-web':
          if (params.query) {
            this.handleSearchAction(id, params.query)
            return
          }
          error = 'Missing query parameter'
          break

        case 'read-file':
          this.handleReadFileAction(id, params.path)
          return

        case 'tail-logs':
          this.handleTailLogsAction(id, params.appId, params.lines)
          return

        default:
          error = `Unknown action: ${action}`
      }
    } catch (e) {
      error = e instanceof Error ? e.message : 'Unknown error'
    }

    this.postToIframe({
      type: 'action:response',
      id,
      success,
      error,
    } satisfies ArchyActionResponse)
  }

  private async handleSearchAction(id: string, query: string) {
    const appStore = useAppStore()
    const packages = appStore.packages || {}
    const searxng = packages['searxng']

    if (!searxng || searxng.state !== 'installed' || searxng.installed?.status !== 'running') {
      this.postToIframe({
        type: 'action:response',
        id,
        success: false,
        error: 'SearXNG is not installed or not running',
      } satisfies ArchyActionResponse)
      return
    }

    try {
      const response = await fetch(`/apps/searxng/search?q=${encodeURIComponent(query)}&format=json`)
      const results: unknown = await response.json()
      this.postToIframe({
        type: 'action:response',
        id,
        success: true,
        data: results,
      } as ArchyActionResponse & { data: unknown })
    } catch (err) {
      this.postToIframe({
        type: 'action:response',
        id,
        success: false,
        error: err instanceof Error ? err.message : 'Search failed',
      } satisfies ArchyActionResponse)
    }
  }

  private getAppUrl(appId: string): string | null {
    const appStore = useAppStore()
    const packages = appStore.packages || {}
    const pkg = packages[appId]
    if (pkg?.installed?.status === 'running') {
      const ifaces = pkg.installed['interface-addresses']
      if (ifaces) {
        const main = ifaces['main'] || Object.values(ifaces)[0]
        if (main?.['lan-address']) return main['lan-address']
      }
    }
    const containerStore = useContainerStore()
    const containers = containerStore.containers
    const container = containers.find(c => c.name === appId || c.name === `archy-${appId}`)
    if (container?.lan_address) return container.lan_address
    const bundled = BUNDLED_APPS.find(a => a.id === appId)
    if (bundled?.ports?.[0]) return `/apps/${appId}/`
    return null
  }

  private async fetchAndSanitize(category: AIContextCategory, _query?: string): Promise<unknown> {
    const appStore = useAppStore()

    switch (category) {
      case 'apps': return this.sanitizeApps(appStore)
      case 'system': return await this.sanitizeSystem(appStore)
      case 'network': return this.sanitizeNetwork(appStore)
      case 'wallet': return this.sanitizeWallet(appStore)
      case 'files': return this.sanitizeFiles()
      case 'bitcoin': return this.sanitizeBitcoin(appStore)
      case 'media': return this.sanitizeMedia(appStore)
      case 'search': return this.sanitizeSearch(appStore)
      case 'ai-local': return this.sanitizeAILocal(appStore)
      case 'notes': return this.sanitizeNotes()
      default: return null
    }
  }

  // T4: Enhanced apps with version, health, URL, web UI info
  private sanitizeApps(store: ReturnType<typeof useAppStore>): unknown {
    const packages = store.packages || {}
    const containerStore = useContainerStore()

    const apps = Object.entries(packages).map(([id, pkg]) => {
      const hasWebUI = !!pkg.manifest?.interfaces?.main?.ui
      const url = hasWebUI ? `/apps/${id}/` : null
      return {
        id,
        name: pkg.manifest?.title || id,
        version: pkg.manifest?.version || 'unknown',
        state: pkg.state || 'unknown',
        status: pkg.installed?.status || 'unknown',
        hasWebUI,
        url,
      }
    })

    const bundledApps = containerStore.containers.map(c => ({
      id: c.name,
      name: BUNDLED_APPS.find(b => b.id === c.name)?.name || c.name,
      state: c.state === 'running' ? 'installed' : 'stopped',
      status: c.state,
      hasWebUI: !!(BUNDLED_APPS.find(b => b.id === c.name)?.ports?.length),
      url: c.lan_address || null,
    }))

    return [...apps, ...bundledApps]
  }

  // T5: Real system metrics from RPC
  private async sanitizeSystem(store: ReturnType<typeof useAppStore>): Promise<unknown> {
    const info = store.serverInfo
    const base = {
      version: info?.version || 'unknown',
      name: info?.name || 'Archipelago',
    }

    try {
      const [metrics, time] = await Promise.all([
        rpcClient.call<{ cpu: number; disk: { used: number; total: number }; memory: { used: number; total: number } }>({ method: 'server.metrics' }),
        rpcClient.call<{ now: string; uptime: number }>({ method: 'server.time' }),
      ])
      return {
        ...base,
        cpu: metrics.cpu,
        memory: { used: metrics.memory.used, total: metrics.memory.total },
        disk: { used: metrics.disk.used, total: metrics.disk.total },
        uptime: time.uptime,
      }
    } catch {
      return { ...base, status: 'metrics unavailable' }
    }
  }

  // T6: Network with peer count and Tor/Tailscale status
  private sanitizeNetwork(store: ReturnType<typeof useAppStore>): unknown {
    const info = store.serverInfo
    const containerStore = useContainerStore()
    const tailscale = containerStore.containers.find(c => c.name === 'tailscale')
    const hasTor = !!info?.['tor-address']

    return {
      connected: store.isConnected,
      torConnected: hasTor,
      tailscaleActive: tailscale?.state === 'running',
    }
  }

  // T7: Bitcoin status + deep data from backend RPC
  private async sanitizeBitcoin(store: ReturnType<typeof useAppStore>): Promise<unknown> {
    const packages = store.packages || {}
    const containerStore = useContainerStore()

    const btcPkg = packages['bitcoind'] || packages['bitcoin-core'] || packages['bitcoin']
    const btcContainer = containerStore.containers.find(c =>
      c.name === 'bitcoin-knots' || c.name === 'archy-bitcoin-knots'
    )

    const isRunning = (btcPkg?.installed?.status === 'running') ||
      (btcContainer?.state === 'running')

    if (!isRunning) {
      return { available: false, message: 'Bitcoin Core not running' }
    }

    try {
      const info = await rpcClient.call<{
        block_height: number
        sync_progress: number
        chain: string
        difficulty: number
        mempool_size: number
        mempool_tx_count: number
        verification_progress: number
      }>({ method: 'bitcoin.getinfo' })
      return { available: true, status: 'running', ...info }
    } catch {
      return { available: true, status: 'running', network: 'mainnet' }
    }
  }

  // T8: Media libraries from installed media apps
  private sanitizeMedia(store: ReturnType<typeof useAppStore>): unknown {
    const packages = store.packages || {}
    const mediaAppIds = ['plex', 'jellyfin', 'navidrome', 'nextcloud']
    const libraries: { source: string; name: string; status: string }[] = []

    for (const id of mediaAppIds) {
      const pkg = packages[id]
      if (pkg && (pkg.state === 'installed' || pkg.state === 'running' || pkg.state === 'stopped')) {
        libraries.push({
          source: id,
          name: pkg.manifest?.title || id,
          status: pkg.state,
        })
      }
    }

    if (libraries.length === 0) {
      return {
        available: false,
        libraries: [],
        message: 'No media apps installed. Install Plex or Jellyfin from the App Store.',
      }
    }
    return { available: true, libraries }
  }

  // T9: Files from FileBrowser
  private async sanitizeFiles(): Promise<unknown> {
    try {
      if (!fileBrowserClient.isAuthenticated) {
        const ok = await fileBrowserClient.login()
        if (!ok) return { available: false, message: 'File browser authentication failed' }
      }
      const usage = await fileBrowserClient.getUsage()
      const items = await fileBrowserClient.listDirectory('/')
      const folders = items.filter(i => i.isDir).map(i => ({ name: i.name, path: i.path }))
      const recentFiles = items
        .filter(i => !i.isDir)
        .sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime())
        .slice(0, 10)
        .map(i => ({ name: i.name, path: i.path, size: i.size, modified: i.modified }))
      return {
        available: true,
        totalSize: usage.totalSize,
        folderCount: usage.folderCount,
        fileCount: usage.fileCount,
        folders,
        recentFiles,
      }
    } catch {
      return { available: false, message: 'File browser not reachable' }
    }
  }

  // T10: SearXNG search engine availability
  private sanitizeSearch(store: ReturnType<typeof useAppStore>): unknown {
    const packages = store.packages || {}
    const searxng = packages['searxng']
    if (!searxng || searxng.state !== 'installed' || searxng.installed?.status !== 'running') {
      return { available: false }
    }
    return { available: true, engine: 'searxng', endpoint: '/apps/searxng/' }
  }

  // T11: Ollama local AI models
  private sanitizeAILocal(store: ReturnType<typeof useAppStore>): unknown {
    const packages = store.packages || {}
    const ollama = packages['ollama']
    if (!ollama || ollama.state !== 'installed' || ollama.installed?.status !== 'running') {
      return { available: false }
    }
    return {
      available: true,
      models: [],
      message: 'Ollama is running. Query /api/tags for model list.',
    }
  }

  // T12: Wallet — LND deep data from backend RPC
  private async sanitizeWallet(store: ReturnType<typeof useAppStore>): Promise<unknown> {
    const packages = store.packages || {}
    const containerStore = useContainerStore()

    const lndPkg = packages['lnd']
    const lndContainer = containerStore.containers.find(c =>
      c.name === 'lnd' || c.name === 'archy-lnd'
    )

    const isRunning = (lndPkg?.installed?.status === 'running') ||
      (lndContainer?.state === 'running')

    if (!isRunning) {
      return { available: false, message: 'Lightning (LND) not running' }
    }

    try {
      const info = await rpcClient.call<{
        alias: string
        num_active_channels: number
        num_peers: number
        synced_to_chain: boolean
        block_height: number
        balance_sats: number
        channel_balance_sats: number
        pending_open_balance: number
      }>({ method: 'lnd.getinfo' })
      return { available: true, status: 'running', ...info }
    } catch {
      return { available: true, status: 'running', message: 'LND is running but detailed info unavailable' }
    }
  }

  // T13: Notes/documents
  private sanitizeNotes(): unknown {
    return {
      available: false,
      documents: [],
      message: 'No note-taking apps installed',
    }
  }

  private static readonly ALLOWED_FILE_DIRS = [
    '/var/lib/archipelago/',
    '/var/log/',
    '/opt/archipelago/',
    '/home/archipelago/',
  ]

  private static readonly SENSITIVE_PATH_PATTERNS = [
    'id_rsa', 'id_ed25519', 'private', 'secret', 'password',
    'seed', '.env', 'wallet', 'macaroon', 'tls.key', 'tls.cert',
    'credentials', 'keystore', 'mnemonic',
  ]

  private isPathAllowed(path: string): boolean {
    const normalized = path.replace(/\/+/g, '/').replace(/\.\.\//g, '')
    const inAllowedDir = ContextBroker.ALLOWED_FILE_DIRS.some(dir => normalized.startsWith(dir))
    if (!inAllowedDir) return false
    const lower = normalized.toLowerCase()
    return !ContextBroker.SENSITIVE_PATH_PATTERNS.some(pattern => lower.includes(pattern))
  }

  private async handleReadFileAction(id: string, path?: string) {
    const perms = useAIPermissionsStore()
    if (!perms.isEnabled('files')) {
      this.postToIframe({ type: 'action:response', id, success: false, error: 'File access not permitted' } satisfies ArchyActionResponse)
      return
    }
    if (!path) {
      this.postToIframe({ type: 'action:response', id, success: false, error: 'Missing path parameter' } satisfies ArchyActionResponse)
      return
    }
    if (!this.isPathAllowed(path)) {
      this.postToIframe({ type: 'action:response', id, success: false, error: 'Access denied: path is outside allowed directories or contains sensitive patterns' } satisfies ArchyActionResponse)
      return
    }
    try {
      if (!fileBrowserClient.isAuthenticated) {
        const ok = await fileBrowserClient.login()
        if (!ok) throw new Error('FileBrowser authentication failed')
      }
      const result = await fileBrowserClient.readFileAsText(path)
      this.postToIframe({
        type: 'action:response', id, success: true,
        data: { content: result.content, truncated: result.truncated, size: result.size, path },
      } as ArchyActionResponse)
    } catch (err) {
      this.postToIframe({
        type: 'action:response', id, success: false,
        error: err instanceof Error ? err.message : 'Failed to read file',
      } satisfies ArchyActionResponse)
    }
  }

  private async handleTailLogsAction(id: string, appId?: string, linesStr?: string) {
    const perms = useAIPermissionsStore()
    if (!perms.isEnabled('apps')) {
      this.postToIframe({ type: 'action:response', id, success: false, error: 'App access not permitted' } satisfies ArchyActionResponse)
      return
    }
    if (!appId) {
      this.postToIframe({ type: 'action:response', id, success: false, error: 'Missing appId parameter' } satisfies ArchyActionResponse)
      return
    }
    const lines = Math.min(parseInt(linesStr || '50', 10) || 50, 200)
    try {
      const logs = await rpcClient.call<string[]>({ method: 'container-logs', params: { app_id: appId, lines } })
      const redactedLogs = logs.map(line => ContextBroker.redactLogLine(line))
      this.postToIframe({
        type: 'action:response', id, success: true,
        data: { appId, lines: redactedLogs, count: redactedLogs.length },
      } as ArchyActionResponse)
    } catch (err) {
      this.postToIframe({
        type: 'action:response', id, success: false,
        error: err instanceof Error ? err.message : 'Failed to fetch logs',
      } satisfies ArchyActionResponse)
    }
  }

  private static redactLogLine(line: string): string {
    // Redact RPC passwords (e.g., rpcpassword=xxx)
    let redacted = line.replace(/(?:rpcpassword|rpcauth|password|passwd|secret|token|apikey|api_key|macaroon)[\s]*[=:]\s*\S+/gi, '$&'.replace(/[=:]\s*\S+/, '=[REDACTED]'))
    // More targeted: key=value patterns
    redacted = redacted.replace(/((?:password|secret|token|apikey|api_key|macaroon|rpcpassword|rpcauth)\s*[=:]\s*)\S+/gi, '$1[REDACTED]')
    // Redact long hex strings (>32 chars, likely private keys)
    redacted = redacted.replace(/\b[0-9a-fA-F]{64,}\b/g, '[REDACTED_KEY]')
    // Redact base64 macaroon values (long base64 strings)
    redacted = redacted.replace(/\b[A-Za-z0-9+/]{64,}={0,2}\b/g, '[REDACTED_TOKEN]')
    return redacted
  }

  private postToIframe(msg: ArchyResponse) {
    if (!this.iframe.value?.contentWindow) return
    this.iframe.value.contentWindow.postMessage(msg, this.allowedOrigin)
  }
}
