/**
 * Container Sync
 *
 * Handles synchronization of persistent workspace stores with the user container
 * via MCP tools. Persistent stores (chartStore, userPreferences, etc.) are
 * stored in the container and loaded/saved via MCP tool calls.
 *
 * Container-side storage: /data/workspace/{store_name}.json
 *
 * MCP Tools used:
 * - WorkspaceRead(store_name) -> dict
 * - WorkspaceWrite(store_name, data) -> None
 * - WorkspacePatch(store_name, patch) -> dict (new state)
 */

import type { FastifyBaseLogger } from 'fastify';
import type { Operation as JsonPatchOp } from 'fast-json-patch';
import type { MCPClientConnector } from '../harness/mcp-client.js';

/**
 * Result of loading a store from the container.
 */
export interface LoadResult {
  exists: boolean;
  state?: unknown;
  error?: string;
}

/**
 * Result of saving a store to the container.
 */
export interface SaveResult {
  success: boolean;
  error?: string;
}

/**
 * Result of patching a store in the container.
 */
export interface PatchResult {
  success: boolean;
  newState?: unknown;
  error?: string;
}

/**
 * Handles synchronization with the user's container via MCP.
 */
export class ContainerSync {
  private mcpClient: MCPClientConnector;
  private logger: FastifyBaseLogger;

  constructor(mcpClient: MCPClientConnector, logger: FastifyBaseLogger) {
    this.mcpClient = mcpClient;
    this.logger = logger.child({ component: 'ContainerSync' });
  }

  /**
   * Parse a raw MCP callTool response into the tool's return value.
   * MCP tool results are wrapped as: { content: [{ type: 'text', text: '<json>' }] }
   */
  private parseMcpResult(raw: unknown): unknown {
    const r = raw as any;
    const text = r?.content?.[0]?.text ?? r?.[0]?.text;
    if (typeof text === 'string') {
      return JSON.parse(text);
    }
    // Already unwrapped (shouldn't happen in practice)
    return raw;
  }

  /**
   * Load a workspace store from the container.
   * Returns the stored state or indicates the store doesn't exist.
   */
  async loadStore(storeName: string): Promise<LoadResult> {
    if (!this.mcpClient.isConnected()) {
      this.logger.warn({ store: storeName }, 'MCP client not connected, cannot load store');
      return { exists: false, error: 'MCP client not connected' };
    }

    try {
      this.logger.debug({ store: storeName }, 'Loading store from container');

      const result = this.parseMcpResult(await this.mcpClient.callTool('WorkspaceRead', {
        store_name: storeName,
      })) as { exists: boolean; data?: unknown; error?: string };

      if (result.error) {
        this.logger.warn({ store: storeName, error: result.error }, 'Container returned error');
        return { exists: false, error: result.error };
      }

      if (!result.exists) {
        this.logger.debug({ store: storeName }, 'Store does not exist in container');
        return { exists: false };
      }

      this.logger.debug({ store: storeName }, 'Loaded store from container');
      return { exists: true, state: result.data };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      this.logger.error({ store: storeName, error: message }, 'Failed to load store from container');
      return { exists: false, error: message };
    }
  }

  /**
   * Save a workspace store to the container.
   * Overwrites any existing state.
   */
  async saveStore(storeName: string, state: unknown): Promise<SaveResult> {
    if (!this.mcpClient.isConnected()) {
      this.logger.warn({ store: storeName }, 'MCP client not connected, cannot save store');
      return { success: false, error: 'MCP client not connected' };
    }

    try {
      this.logger.debug({ store: storeName }, 'Saving store to container');

      const result = this.parseMcpResult(await this.mcpClient.callTool('WorkspaceWrite', {
        store_name: storeName,
        data: state,
      })) as { success: boolean; error?: string };

      if (result.error || !result.success) {
        this.logger.warn({ store: storeName, error: result.error }, 'Failed to save store');
        return { success: false, error: result.error || 'Unknown error' };
      }

      this.logger.debug({ store: storeName }, 'Saved store to container');
      return { success: true };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      this.logger.error({ store: storeName, error: message }, 'Failed to save store to container');
      return { success: false, error: message };
    }
  }

  /**
   * Apply a JSON patch to a store in the container.
   * Returns the new state after applying the patch.
   */
  async patchStore(storeName: string, patch: JsonPatchOp[]): Promise<PatchResult> {
    if (!this.mcpClient.isConnected()) {
      this.logger.warn({ store: storeName }, 'MCP client not connected, cannot patch store');
      return { success: false, error: 'MCP client not connected' };
    }

    try {
      this.logger.debug({ store: storeName, patchOps: patch.length }, 'Patching store in container');

      const result = this.parseMcpResult(await this.mcpClient.callTool('WorkspacePatch', {
        store_name: storeName,
        patch,
      })) as { success: boolean; data?: unknown; error?: string };

      if (result.error || !result.success) {
        this.logger.warn({ store: storeName, error: result.error }, 'Failed to patch store');
        return { success: false, error: result.error || 'Unknown error' };
      }

      this.logger.debug({ store: storeName }, 'Patched store in container');
      return { success: true, newState: result.data };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      this.logger.error({ store: storeName, error: message }, 'Failed to patch store in container');
      return { success: false, error: message };
    }
  }

  /**
   * Load all persistent stores from the container.
   * Returns a map of store name -> state.
   */
  async loadAllStores(storeNames: string[]): Promise<Map<string, unknown>> {
    const states = new Map<string, unknown>();

    for (const storeName of storeNames) {
      const result = await this.loadStore(storeName);
      if (result.exists && result.state !== undefined) {
        states.set(storeName, result.state);
      }
    }

    return states;
  }

  /**
   * Save all persistent stores to the container.
   */
  async saveAllStores(stores: Map<string, unknown>): Promise<void> {
    for (const [storeName, state] of stores) {
      await this.saveStore(storeName, state);
    }
  }

  /**
   * Check if MCP client is connected.
   */
  isConnected(): boolean {
    return this.mcpClient.isConnected();
  }
}
