import { Annotation } from '@langchain/langgraph';
import type { FastifyBaseLogger } from 'fastify';
import type { UserContext } from '../memory/session-context.js';

/**
 * Workflow configuration (loaded from config.yaml)
 */
export interface WorkflowConfig {
  name: string;
  description: string;
  timeout?: number; // Milliseconds
  maxRetries?: number;
  requiresApproval?: boolean;
  approvalNodes?: string[]; // Nodes that require human approval
}

/**
 * Base workflow state (all workflows extend this)
 */
export const BaseWorkflowState = Annotation.Root({
  userContext: Annotation<UserContext>(),
  input: Annotation<string>(),
  output: Annotation<string | null>({
    value: (left, right) => right ?? left,
    default: () => null,
  }),
  error: Annotation<string | null>({
    value: (left, right) => right ?? left,
    default: () => null,
  }),
  metadata: Annotation<Record<string, unknown>>({
    value: (left, right) => ({ ...left, ...right }),
    default: () => ({}),
  }),
});

export type BaseWorkflowStateType = typeof BaseWorkflowState.State;

/**
 * Workflow node function type
 */
export type WorkflowNode<TState> = (state: TState) => Promise<Partial<TState>>;

/**
 * Workflow edge condition function type
 */
export type WorkflowEdgeCondition<TState> = (state: TState) => string;

/**
 * Base workflow class
 *
 * Workflows are LangGraph state machines with:
 * - Config-driven setup (timeout, retries, approval gates)
 * - Standardized state structure
 * - Support for human-in-the-loop
 * - Validation loops
 * - Error handling
 *
 * Structure:
 * workflows/
 *   strategy-validation/
 *     config.yaml
 *     graph.ts
 *     nodes.ts
 *     state.ts
 */
export abstract class BaseWorkflow<TState extends BaseWorkflowStateType> {
  protected logger: FastifyBaseLogger;
  protected config: WorkflowConfig;
  protected graph?: any;

  constructor(config: WorkflowConfig, logger: FastifyBaseLogger) {
    this.config = config;
    this.logger = logger;
  }

  /**
   * Build the workflow graph (implemented by subclasses)
   */
  abstract buildGraph(): any;

  /**
   * Compile the workflow graph
   */
  compile(): void {
    this.logger.info({ workflow: this.config.name }, 'Compiling workflow graph');
    const stateGraph = this.buildGraph();
    this.graph = stateGraph.compile();
  }

  /**
   * Execute the workflow
   */
  async execute(initialState: Partial<TState>): Promise<TState> {
    if (!this.graph) {
      throw new Error('Workflow not compiled. Call compile() first.');
    }

    this.logger.info(
      { workflow: this.config.name, userId: initialState.userContext?.userId },
      'Executing workflow'
    );

    const startTime = Date.now();

    try {
      // Execute with timeout if configured
      const result = this.config.timeout
        ? await this.executeWithTimeout(initialState)
        : await this.graph.invoke(initialState);

      const duration = Date.now() - startTime;

      this.logger.info(
        {
          workflow: this.config.name,
          duration,
          success: !result.error,
        },
        'Workflow execution completed'
      );

      return result;
    } catch (error) {
      this.logger.error(
        { error, workflow: this.config.name },
        'Workflow execution failed'
      );
      throw error;
    }
  }

  /**
   * Stream workflow execution
   */
  async *stream(initialState: Partial<TState>): AsyncGenerator<TState> {
    if (!this.graph) {
      throw new Error('Workflow not compiled. Call compile() first.');
    }

    this.logger.info(
      { workflow: this.config.name, userId: initialState.userContext?.userId },
      'Streaming workflow execution'
    );

    try {
      const stream = await this.graph.stream(initialState);

      for await (const state of stream) {
        yield state;
      }
    } catch (error) {
      this.logger.error(
        { error, workflow: this.config.name },
        'Workflow streaming failed'
      );
      throw error;
    }
  }

  /**
   * Execute with timeout
   */
  private async executeWithTimeout(initialState: Partial<TState>): Promise<TState> {
    if (!this.config.timeout || !this.graph) {
      throw new Error('Invalid state');
    }

    return await Promise.race([
      this.graph.invoke(initialState) as Promise<TState>,
      new Promise<TState>((_, reject) =>
        setTimeout(
          () => reject(new Error(`Workflow timeout after ${this.config.timeout}ms`)),
          this.config.timeout
        )
      ),
    ]);
  }

  /**
   * Get workflow name
   */
  getName(): string {
    return this.config.name;
  }

  /**
   * Check if workflow requires approval
   */
  requiresApproval(): boolean {
    return this.config.requiresApproval || false;
  }

  /**
   * Get approval nodes
   */
  getApprovalNodes(): string[] {
    return this.config.approvalNodes || [];
  }
}
