import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';

// Define interfaces for type safety
interface AgentOutput {
  output?: any;
  completed: boolean;
  error?: string;
  executionTime?: number;
  retryCount?: number;
  startTime?: number;
}

interface Context {
  specId: string;
  featurePrompt: string;
  spec: any;
  agents: Record<string, AgentOutput>;
  finalBundle: any;
  log: string[];
  metadata: {
    createdAt: string;
    lastUpdated: string;
    version: string;
  };
}

let currentContext: Context | null = null;
const sessionsDir = path.join(process.cwd(), 'sessions');
const backupDir = path.join(sessionsDir, 'backups');

// Ensure directories exist
const ensureDirectories = async () => {
  await fs.mkdir(sessionsDir, { recursive: true });
  await fs.mkdir(backupDir, { recursive: true });
};

const generateSpecId = () => {
  const date = new Date();
  const year = date.getFullYear();
  const month = (date.getMonth() + 1).toString().padStart(2, '0');
  const day = date.getDate().toString().padStart(2, '0');
  const randomId = Math.random().toString(36).substring(2, 6).toUpperCase();
  return `SPEC-${year}${month}${day}-${randomId}`;
};

const validateContext = (context: any): Context => {
  if (!context.specId || typeof context.specId !== 'string') {
    throw new Error('Invalid specId in context');
  }
  if (!context.featurePrompt || typeof context.featurePrompt !== 'string') {
    throw new Error('Invalid featurePrompt in context');
  }
  if (!Array.isArray(context.log)) {
    throw new Error('Invalid log in context');
  }
  return context as Context;
};

const validateAgentOutput = (output: any): AgentOutput => {
  if (typeof output.completed !== 'boolean') {
    throw new Error('Agent output must have completed boolean');
  }
  return output as AgentOutput;
};

export const resetContext = (): void => {
  currentContext = null;
};

export const createContext = (featurePrompt: string): string => {
  const specId = generateSpecId();
  const now = new Date().toISOString();
  
  currentContext = {
    specId,
    featurePrompt,
    spec: {},
    agents: {},
    finalBundle: {},
    log: [],
    metadata: {
      createdAt: now,
      lastUpdated: now,
      version: '1.0.0',
    },
  };
  return currentContext.specId;
};

export const getContext = (): Context => {
  if (!currentContext) {
    throw new Error('Context not initialized. Call createContext first.');
  }
  return currentContext;
};

export const updateContext = (updates: Partial<Context>): Context => {
  if (!currentContext) {
    throw new Error('Context not initialized. Call createContext first.');
  }
  
  const updatedContext = {
    ...currentContext,
    ...updates,
    metadata: {
      ...currentContext.metadata,
      ...updates.metadata,
      lastUpdated: new Date().toISOString(),
    },
  };
  
  try {
    currentContext = validateContext(updatedContext);
    return currentContext;
  } catch (error) {
    console.warn('Context validation failed, keeping previous context:', error);
    throw new Error(`Context update failed validation: ${error}`);
  }
};

export const updateAgentOutput = (agentName: string, output: Partial<AgentOutput>): void => {
  if (!currentContext) {
    throw new Error('Context not initialized');
  }
  
  const existingAgent = currentContext.agents[agentName] || { completed: false };
  const updatedAgent = { ...existingAgent, ...output };
  
  try {
    validateAgentOutput(updatedAgent);
    updateContext({
      agents: {
        ...currentContext.agents,
        [agentName]: updatedAgent,
      },
    });
  } catch (error) {
    console.error(`Agent output validation failed for ${agentName}:`, error);
    throw new Error(`Agent output validation failed: ${error}`);
  }
};

export const logToContext = (message: string): void => {
  if (currentContext) {
    currentContext.log.push(message);
    currentContext.metadata.lastUpdated = new Date().toISOString();
  }
};

const createBackup = async (filePath: string): Promise<string> => {
  try {
    const stats = await fs.stat(filePath);
    if (stats.isFile()) {
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const fileName = path.basename(filePath, '.json');
      const backupPath = path.join(backupDir, `${fileName}-${timestamp}.json`);
      
      await fs.copyFile(filePath, backupPath);
      console.log(`💾 Backup created: ${backupPath}`);
      return backupPath;
    }
  } catch (error: any) {
    // Silently handle ENOENT (file doesn't exist yet) - this is normal on first save
    if (error.code !== 'ENOENT') {
      console.warn('Failed to create backup:', error);
    }
  }
  return '';
};

export const saveContext = async (createBackupFlag = true): Promise<string> => {
  if (!currentContext) {
    throw new Error('Context not initialized.');
  }

  await ensureDirectories();

  try {
    validateContext(currentContext);
  } catch (error) {
    throw new Error(`Cannot save invalid context: ${error}`);
  }

  const filePath = path.join(sessionsDir, `${currentContext.specId}.json`);
  
  // Create backup if file exists and backup is requested
  if (createBackupFlag) {
    await createBackup(filePath);
  }
  
  // Write with atomic operation (write to temp file first)
  const tempPath = `${filePath}.tmp`;
  
  try {
    await fs.writeFile(tempPath, JSON.stringify(currentContext, null, 2));
    await fs.rename(tempPath, filePath);
    console.log(`✅ Context saved successfully: ${filePath}`);
    return filePath;
  } catch (error) {
    // Clean up temp file if it exists
    try {
      await fs.unlink(tempPath);
    } catch {}
    throw new Error(`Failed to save context: ${error}`);
  }
};

export const loadContext = async (specId: string): Promise<Context> => {
  const filePath = path.join(sessionsDir, `${specId}.json`);
  
  try {
    const data = await fs.readFile(filePath, 'utf-8');
    const parsedContext = JSON.parse(data);
    
    currentContext = validateContext(parsedContext);
    console.log(`✅ Context loaded successfully: ${filePath}`);
    return currentContext;
  } catch (error) {
    throw new Error(`Failed to load or validate context from ${filePath}: ${error}`);
  }
};

// Utility functions for agent management
export const getAgentStatus = (agentName: string): AgentOutput | null => {
  if (!currentContext) return null;
  return currentContext.agents[agentName] || null;
};

export const isAgentCompleted = (agentName: string): boolean => {
  const status = getAgentStatus(agentName);
  return status?.completed === true;
};

export const getFailedAgents = (): string[] => {
  if (!currentContext) return [];
  return Object.entries(currentContext.agents)
    .filter(([_, agent]) => agent.error && !agent.completed)
    .map(([name]) => name);
};

export const getCompletedAgents = (): string[] => {
  if (!currentContext) return [];
  return Object.entries(currentContext.agents)
    .filter(([_, agent]) => agent.completed)
    .map(([name]) => name);
};

export const getPendingAgents = (allAgents: string[]): string[] => {
  if (!currentContext) return allAgents;
  return allAgents.filter(agent => !isAgentCompleted(agent));
};

export const getContextStats = () => {
  if (!currentContext) return null;
  
  const agents = Object.keys(currentContext.agents);
  const completed = getCompletedAgents();
  const failed = getFailedAgents();
  const pending = agents.filter(agent => !completed.includes(agent) && !failed.includes(agent));
  
  return {
    total: agents.length,
    completed: completed.length,
    failed: failed.length,
    pending: pending.length,
    completionRate: agents.length > 0 ? (completed.length / agents.length) * 100 : 0,
  };
};

// Export types
export type { Context, AgentOutput };
