/**
 * MCP Client Service for Anything Copilot
 * Connects to MCP SuperAssistant Proxy via SSE
 */

export interface McpTool {
  name: string
  description: string
  inputSchema?: Record<string, any>
}

export interface McpExecutionResult {
  id: string
  toolName: string
  args: Record<string, any>
  status: "pending" | "success" | "error"
  result?: any
  error?: string
  timestamp: number
  duration?: number
}

export type McpConnectionStatus = "disconnected" | "connecting" | "connected" | "error"

type StatusCallback = (status: McpConnectionStatus) => void
type ToolsCallback = (tools: McpTool[]) => void
type ExecutionCallback = (result: McpExecutionResult) => void

export class McpService {
  private serverUrl: string
  private abortController: AbortController | null = null
  private sessionId: string | null = null
  private status: McpConnectionStatus = "disconnected"
  private tools: McpTool[] = []
  private executions: McpExecutionResult[] = []

  private statusListeners: StatusCallback[] = []
  private toolsListeners: ToolsCallback[] = []
  private executionListeners: ExecutionCallback[] = []

  private pendingRequests = new Map<
    string | number,
    { resolve: (value: any) => void; reject: (error: any) => void }
  >()
  private requestId = 0

  // n8n credentials loaded from storage
  private n8nApiUrl: string = ""
  private n8nApiKey: string = ""
  private mcpAuthToken: string = ""

  private static readonly STORAGE_KEY = "mcp_n8n_config"

  constructor(serverUrl = "http://localhost:3006") {
    this.serverUrl = serverUrl
  }

  /** Load config from chrome.storage.local (keys saved by McpSettings.vue) */
  async loadConfig(): Promise<void> {
    try {
      const data = await chrome.storage.local.get(McpService.STORAGE_KEY)
      const cfg = data[McpService.STORAGE_KEY]
      if (cfg) {
        if (cfg.proxyUrl) this.serverUrl = cfg.proxyUrl
        if (cfg.n8nApiUrl) this.n8nApiUrl = cfg.n8nApiUrl
        if (cfg.n8nApiKey) this.n8nApiKey = cfg.n8nApiKey
        if (cfg.mcpAuthToken !== undefined) this.mcpAuthToken = cfg.mcpAuthToken
        console.log("[McpService] Loaded config from storage:", {
          proxyUrl: this.serverUrl,
          n8nApiUrl: this.n8nApiUrl,
          hasKey: !!this.n8nApiKey,
          hasAuthToken: !!this.mcpAuthToken,
        })
      } else {
        console.warn("[McpService] No saved config found — configure in sidebar settings")
      }
    } catch (e) {
      console.warn("[McpService] Could not load config:", e)
    }
  }

  getN8nCredentials() {
    return { n8nApiUrl: this.n8nApiUrl, n8nApiKey: this.n8nApiKey }
  }

  // --- Event Subscription ---
  onStatusChange(callback: StatusCallback) {
    this.statusListeners.push(callback)
    return () => {
      this.statusListeners = this.statusListeners.filter((cb) => cb !== callback)
    }
  }

  onToolsUpdate(callback: ToolsCallback) {
    this.toolsListeners.push(callback)
    return () => {
      this.toolsListeners = this.toolsListeners.filter((cb) => cb !== callback)
    }
  }

  onExecution(callback: ExecutionCallback) {
    this.executionListeners.push(callback)
    return () => {
      this.executionListeners = this.executionListeners.filter((cb) => cb !== callback)
    }
  }

  private setStatus(status: McpConnectionStatus) {
    this.status = status
    this.statusListeners.forEach((cb) => cb(status))
  }

  getStatus(): McpConnectionStatus {
    return this.status
  }

  getTools(): McpTool[] {
    return this.tools
  }

  getExecutions(): McpExecutionResult[] {
    return this.executions
  }

  // --- Connection ---
  async connect(): Promise<void> {
    if (this.status === "connected" || this.status === "connecting") {
      return
    }

    this.setStatus("connecting")

    try {
      // First check health
      const healthOk = await this.checkHealth()
      if (!healthOk) {
        this.setStatus("error")
        return
      }

      // Connect via custom fetch SSE
      this.abortController = new AbortController()

      const headers: Record<string, string> = {
        "x-n8n-url": this.n8nApiUrl,
        "x-n8n-key": this.n8nApiKey,
      }
      if (this.mcpAuthToken) {
        headers["Authorization"] = `Bearer ${this.mcpAuthToken}`
      }

      fetch(`${this.serverUrl}/sse`, {
        method: "GET",
        headers,
        signal: this.abortController.signal,
      }).then(async (response) => {
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`)
        }
        console.log("[McpService] SSE connection opened")
        this.setStatus("connected")

        const reader = response.body?.getReader()
        if (!reader) throw new Error("No reader")

        const decoder = new TextDecoder()
        let buffer = ""

        try {
          while (true) {
            const { done, value } = await reader.read()
            if (done) break
            buffer += decoder.decode(value, { stream: true })
            const messages = buffer.split(/\r?\n\r?\n/)
            buffer = messages.pop() || ""

            for (const msg of messages) {
              let event = ""
              let data = ""
              for (const line of msg.split(/\r?\n/)) {
                if (line.startsWith("event: ")) event = line.substring(7)
                else if (line.startsWith("event:")) event = line.substring(6)
                else if (line.startsWith("data: ")) data += line.substring(6)
                else if (line.startsWith("data:")) data += line.substring(5)
              }

              if (event === "endpoint" && data) {
                this.sessionId = data
                console.log("[McpService] Got session endpoint:", data)
                this.fetchTools()
              } else if (data) {
                try {
                  this.handleMessage(JSON.parse(data))
                } catch (e) {}
              }
            }
          }
        } catch (e: any) {
          if (e.name !== "AbortError") {
            console.error("[McpService] SSE stream error:", e)
            this.setStatus("error")
          }
        }
      }).catch((error) => {
        console.error("[McpService] SSE fetch failed:", error)
        if (this.status !== "error") {
          this.setStatus("error")
        }
      })
    } catch (error) {
      console.error("[McpService] Connection preamble failed:", error)
      this.setStatus("error")
    }
  }

  async disconnect(): Promise<void> {
    if (this.abortController) {
      this.abortController.abort()
      this.abortController = null
    }
    this.sessionId = null
    this.setStatus("disconnected")
  }

  async checkHealth(): Promise<boolean> {
    try {
      const headers: Record<string, string> = {}
      if (this.mcpAuthToken) {
        headers["Authorization"] = `Bearer ${this.mcpAuthToken}`
      }
      const response = await fetch(`${this.serverUrl}/health`, {
        method: "GET",
        headers,
        signal: AbortSignal.timeout(5000),
      })
      return response.ok
    } catch {
      return false
    }
  }

  // --- JSON-RPC Messaging ---
  private async sendRequest(method: string, params?: any): Promise<any> {
    const id = ++this.requestId

    const message = {
      jsonrpc: "2.0",
      id,
      method,
      params: params || {},
    }

    // Determine the endpoint to send to
    let endpoint = `${this.serverUrl}/message`
    if (this.sessionId) {
      // If sessionId is a full URL path, use it
      if (this.sessionId.startsWith("/") || this.sessionId.startsWith("http")) {
        endpoint = this.sessionId.startsWith("http")
          ? this.sessionId
          : `${this.serverUrl}${this.sessionId}`
      }
    }

    return new Promise((resolve, reject) => {
      this.pendingRequests.set(id, { resolve, reject })

      // Set timeout
      setTimeout(() => {
        if (this.pendingRequests.has(id)) {
          this.pendingRequests.delete(id)
          reject(new Error("Request timeout"))
        }
      }, 30000)

      const headers: Record<string, string> = { "Content-Type": "application/json" }
      if (this.mcpAuthToken) {
        headers["Authorization"] = `Bearer ${this.mcpAuthToken}`
      }

      fetch(endpoint, {
        method: "POST",
        headers,
        body: JSON.stringify(message),
      }).catch((error) => {
        this.pendingRequests.delete(id)
        reject(error)
      })
    })
  }

  private handleMessage(data: any) {
    if (data.id && this.pendingRequests.has(data.id)) {
      const { resolve, reject } = this.pendingRequests.get(data.id)!
      this.pendingRequests.delete(data.id)

      if (data.error) {
        reject(data.error)
      } else {
        resolve(data.result)
      }
    }
  }

  // --- Tool Operations ---
  async fetchTools(): Promise<McpTool[]> {
    try {
      const result = await this.sendRequest("tools/list")
      if (result && result.tools) {
        this.tools = result.tools.map((t: any) => ({
          name: t.name,
          description: t.description || "",
          inputSchema: t.inputSchema,
        }))
        this.toolsListeners.forEach((cb) => cb(this.tools))
      }
      return this.tools
    } catch (error) {
      console.error("[McpService] Failed to fetch tools:", error)
      // If JSON-RPC doesn't work, try REST API
      try {
        const headers: Record<string, string> = {}
        if (this.mcpAuthToken) {
          headers["Authorization"] = `Bearer ${this.mcpAuthToken}`
        }
        const response = await fetch(`${this.serverUrl}/tools`, {
          headers,
          signal: AbortSignal.timeout(10000),
        })
        if (response.ok) {
          const data = await response.json()
          const toolsList = data.tools || data || []
          this.tools = toolsList.map((t: any) => ({
            name: t.name,
            description: t.description || "",
            inputSchema: t.inputSchema || t.input_schema || {},
          }))
          this.toolsListeners.forEach((cb) => cb(this.tools))
        }
      } catch (e) {
        console.error("[McpService] REST fallback also failed:", e)
      }
      return this.tools
    }
  }

  async callTool(toolName: string, args: Record<string, any> = {}): Promise<McpExecutionResult> {
    const execution: McpExecutionResult = {
      id: `exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
      toolName,
      args,
      status: "pending",
      timestamp: Date.now(),
    }

    this.executions.unshift(execution)
    this.executionListeners.forEach((cb) => cb(execution))

    const startTime = Date.now()

    try {
      const result = await this.sendRequest("tools/call", {
        name: toolName,
        arguments: args,
      })

      execution.status = "success"
      execution.result = result
      execution.duration = Date.now() - startTime

      this.executionListeners.forEach((cb) => cb(execution))
      return execution
    } catch (error: any) {
      execution.status = "error"
      execution.error = error?.message || String(error)
      execution.duration = Date.now() - startTime

      // Try REST fallback
      try {
        const headers: Record<string, string> = { "Content-Type": "application/json" }
        if (this.mcpAuthToken) {
          headers["Authorization"] = `Bearer ${this.mcpAuthToken}`
        }
        const response = await fetch(`${this.serverUrl}/call-tool`, {
          method: "POST",
          headers,
          body: JSON.stringify({ name: toolName, arguments: args }),
          signal: AbortSignal.timeout(30000),
        })
        if (response.ok) {
          const data = await response.json()
          execution.status = "success"
          execution.result = data
          execution.error = undefined
          execution.duration = Date.now() - startTime
        }
      } catch (e) {
        // Keep original error
      }

      this.executionListeners.forEach((cb) => cb(execution))
      return execution
    }
  }

  clearExecutions(): void {
    this.executions = []
  }
}

// Singleton instance
let _instance: McpService | null = null

export function getMcpService(url?: string): McpService {
  if (!_instance) {
    _instance = new McpService(url)
  }
  return _instance
}
