const express = require('express');
const cors = require('cors');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const OrchestratorAgent = require('./agents/orchestratorAgent');
const ContextManager = require('./agents/contextManager');
const GoddessAgent = require('./agents/goddessAgent');

const app = express();
const PORT = 3001;
const LM_STUDIO_URL = 'http://localhost:1234';

// Initialize orchestrator, context manager, and goddess agent
const orchestrator = new OrchestratorAgent();
const contextManager = new ContextManager();
const goddessAgent = new GoddessAgent();

// Store active goddess conversations in memory
const goddessConversations = new Map();

// TTS settings store (in-memory, can be persisted later)
const ttsSettings = new Map();

// Data directory paths
const DATA_DIR = path.join(__dirname, '..', 'data');
const SESSIONS_DIR = path.join(DATA_DIR, 'sessions');

app.use(cors());
app.use(express.json());

// Ensure directories exist
if (!fs.existsSync(SESSIONS_DIR)) {
  fs.mkdirSync(SESSIONS_DIR, { recursive: true });
}

// ============ Helper Functions ============

function normalizeCharacterName(name) {
  return name.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
}

function loadSession(sessionId) {
  const filePath = path.join(SESSIONS_DIR, `${sessionId}.json`);
  if (fs.existsSync(filePath)) {
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  }
  return null;
}

function saveSession(sessionId, data) {
  const filePath = path.join(SESSIONS_DIR, `${sessionId}.json`);
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}

function createNewSession(title) {
  const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  const session = {
    id: sessionId,
    title: title || 'New Adventure',
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    messages: [],
    
    // Session-specific context (isolated per session)
    context: {
      characters: [],        // Array of character objects
      locations: [],         // Array of location objects
      inventory: [],         // Array of item objects
      quests: [],            // Array of quest objects
      storyState: {},        // World state key-value pairs
      currentLocation: null, // Current location name
    },
    
    // Journal for this session
    journal: {
      summary: '',
      keyEvents: [],
      entries: [],
      characterArcs: {},
      locationHistory: []
    },
    
    // Settings
    systemPrompt: 'You are a skilled narrator and game master for a text-based RPG. Create vivid, immersive descriptions. Maintain consistency with established characters, locations, and plot. Respond to player actions in a way that advances the story naturally. Track character development and world changes carefully.'
  };
  
  saveSession(sessionId, session);
  return session;
}

// ============ Context Building ============

function buildContextPrompt(session) {
  const ctx = session.context || { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
  const journal = session.journal || { summary: '', keyEvents: [], entries: [], characterArcs: {}, locationHistory: [] };
  let context = '';

  // === STORY JOURNAL (Primary context for AI) ===
  context += '\n╔═══════════════════════════════════════════════════════════╗';
  context += '\n║                    📖 STORY JOURNAL                        ║';
  context += '\n╚═══════════════════════════════════════════════════════════╝\n';
  
  if (journal.summary) {
    context += `\n[STORY SUMMARY]\n${journal.summary}\n`;
  }
  
  if (journal.keyEvents && journal.keyEvents.length > 0) {
    context += '\n[KEY EVENTS]\n';
    journal.keyEvents.slice(-10).forEach(event => {
      context += `• ${event.description}\n`;
    });
  }

  // === ACTIVE CHARACTERS ===
  if (ctx.characters && ctx.characters.length > 0) {
    context += '\n╔═══════════════════════════════════════════════════════════╗';
    context += '\n║                    👥 ACTIVE CHARACTERS                    ║';
    context += '\n╚═══════════════════════════════════════════════════════════╝\n';
    
    const uniqueChars = [...new Set(ctx.characters.map(c => normalizeCharacterName(c.name)))];
    ctx.characters.forEach(char => {
      const normalizedName = normalizeCharacterName(char.name);
      if (uniqueChars.includes(normalizedName)) {
        context += `\n[${char.name}]\n`;
        context += `  Description: ${char.description || 'Unknown'}\n`;
        context += `  Traits: ${(char.traits || []).join(', ') || 'None'}\n`;
        context += `  Status: ${char.status || 'Active'}\n`;
        if (char.relationships && Object.keys(char.relationships).length > 0) {
          context += `  Relationships: ${JSON.stringify(char.relationships)}\n`;
        }
        // Add character arc from journal
        if (journal.characterArcs[char.name]) {
          const arc = journal.characterArcs[char.name];
          if (arc.notes) {
            context += `  Arc Notes: ${arc.notes}\n`;
          }
        }
        // Remove from uniqueChars to avoid duplicates
        uniqueChars.splice(uniqueChars.indexOf(normalizedName), 1);
      }
    });
  }

  // === CURRENT LOCATION ===
  if (ctx.currentLocation) {
    const location = ctx.locations.find(l => normalizeCharacterName(l.name) === normalizeCharacterName(ctx.currentLocation));
    if (location) {
      context += '\n╔═══════════════════════════════════════════════════════════╗';
      context += '\n║                    📍 CURRENT LOCATION                     ║';
      context += '\n╚═══════════════════════════════════════════════════════════╝\n';
      context += `\n[${location.name}]\n`;
      context += `  Description: ${location.description || 'Unknown'}\n`;
      context += `  Atmosphere: ${location.atmosphere || 'Neutral'}\n`;
      if (location.pointsOfInterest && location.pointsOfInterest.length > 0) {
        context += `  Points of Interest: ${location.pointsOfInterest.join(', ')}\n`;
      }
    }
  }

  // === LOCATION HISTORY ===
  if (journal.locationHistory && journal.locationHistory.length > 0) {
    context += '\n[LOCATION HISTORY]\n';
    journal.locationHistory.slice(-5).forEach(loc => {
      context += `→ ${loc.name} (${loc.visitedAt})\n`;
    });
  }

  // === PLAYER INVENTORY ===
  const inventory = ctx.inventory || [];
  context += '\n╔═══════════════════════════════════════════════════════════╗';
  context += '\n║                    🎒 PLAYER INVENTORY                     ║';
  context += '\n╚═══════════════════════════════════════════════════════════╝\n';
  
  if (inventory.length === 0) {
    context += '\n[Empty - No items carried]\n';
  } else {
    inventory.forEach((item, idx) => {
      context += `\n[${idx + 1}] ${item.name}`;
      if (item.type) context += ` (Type: ${item.type})`;
      if (item.description) context += `\n    ${item.description}`;
      if (item.effects) context += `\n    Effects: ${item.effects}`;
    });
  }

  // === QUESTS ===
  const questLog = ctx.quests || [];
  context += '\n╔═══════════════════════════════════════════════════════════╗';
  context += '\n║                    📜 ACTIVE QUESTS                        ║';
  context += '\n╚═══════════════════════════════════════════════════════════╝\n';
  
  if (questLog.length === 0) {
    context += '\n[No active quests]\n';
  } else {
    questLog.forEach(quest => {
      context += `\n[${quest.status || 'Active'}] ${quest.title}\n`;
      context += `  ${quest.description}\n`;
      if (quest.objectives) {
        context += `  Objectives: ${quest.objectives.join(', ')}\n`;
      }
    });
  }

  // === RECENT CONVERSATION (Last 8 exchanges for immediate context) ===
  if (session.messages && session.messages.length > 0) {
    context += '\n╔═══════════════════════════════════════════════════════════╗';
    context += '\n║                    📝 RECENT EVENTS                        ║';
    context += '\n╚═══════════════════════════════════════════════════════════╝\n';
    
    const recentMessages = session.messages.slice(-8);
    recentMessages.forEach(msg => {
      const preview = msg.content.substring(0, 150);
      context += `${msg.role === 'user' ? '[Player]' : '[Narrator]'}: ${preview}${msg.content.length > 150 ? '...' : ''}\n`;
    });
  }

  // === WORLD STATE ===
  if (ctx.storyState && Object.keys(ctx.storyState).length > 0) {
    context += '\n╔═══════════════════════════════════════════════════════════╗';
    context += '\n║                    🌍 WORLD STATE                          ║';
    context += '\n╚═══════════════════════════════════════════════════════════╝\n';
    
    Object.entries(ctx.storyState).forEach(([key, value]) => {
      context += `\n[${key}]: ${typeof value === 'object' ? JSON.stringify(value) : value}\n`;
    });
  }

  return context;
}

// ============ GODDESS CHAT ENDPOINTS ============

// Start goddess conversation
app.post('/api/goddess/start', (req, res) => {
  try {
    const conversationId = `goddess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const greeting = goddessAgent.getInitialGreeting();
    
    goddessConversations.set(conversationId, greeting.state);
    
    console.log(`[Goddess] New conversation started: ${conversationId}`);
    
    res.json({
      conversationId,
      response: greeting.response,
      state: greeting.state
    });
  } catch (error) {
    console.error('[Goddess] Start error:', error.message);
    res.status(500).json({ error: 'Failed to start goddess chat' });
  }
});

// Chat with goddess
app.post('/api/goddess/chat', (req, res) => {
  try {
    const { conversationId, message, playerName, currentState } = req.body;
    
    if (!conversationId) {
      return res.status(400).json({ error: 'Conversation ID required' });
    }
    
    let state = goddessConversations.get(conversationId);
    if (!state) {
      const greeting = goddessAgent.getInitialGreeting();
      state = greeting.state;
      goddessConversations.set(conversationId, state);
    }
    
    // CRITICAL: Merge client state (blessingAccepted, item, world, prophecy)
    if (currentState) {
      if (currentState.blessingAccepted !== undefined) state.blessingAccepted = currentState.blessingAccepted;
      if (currentState.item !== undefined) state.item = currentState.item;
      if (currentState.itemOptions !== undefined) state.itemOptions = currentState.itemOptions;
      if (currentState.world !== undefined) state.world = currentState.world;
      if (currentState.prophecy !== undefined) state.prophecy = currentState.prophecy;
      if (currentState.ready !== undefined) state.ready = currentState.ready;
    }
    
    if (playerName && !state.playerName) {
      state.playerName = playerName;
    }
    
    console.log(`[Goddess] Phase: ${state.phase}, Messages: ${state.messages?.length || 0}, Blessing: ${state.blessingAccepted}, Item: ${state.item?.name || 'none'}`);
    
    goddessAgent.generateResponse(state, message || '').then(result => {
      if (result.success) {
        goddessConversations.set(conversationId, result.state);
        
        console.log(`[Goddess] → New phase: ${result.state.phase}, Ready: ${result.state.ready}`);
        
        res.json({
          response: result.response,
          state: result.state,
          ready: result.state.ready
        });
      } else {
        res.status(500).json({ error: 'Goddess response failed' });
      }
    }).catch(err => {
      console.error('[Goddess] Error:', err.message);
      res.status(500).json({ error: 'Failed to get goddess response' });
    });
    
  } catch (error) {
    console.error('[Goddess] Error:', error.message);
    res.status(500).json({ error: 'Failed to process goddess chat' });
  }
});

// Get goddess conversation state
app.get('/api/goddess/:conversationId', (req, res) => {
  const state = goddessConversations.get(req.params.conversationId);
  if (!state) {
    return res.status(404).json({ error: 'Conversation not found' });
  }
  res.json({ state, ready: state.ready });
});

// ============ GAME CHAT ENDPOINT ============

// Create new session
app.post('/api/session', (req, res) => {
  const { title } = req.body;
  const session = createNewSession(title);
  res.json(session);
});

// Get session
app.get('/api/session/:id', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  res.json(session);
});

// List all sessions
app.get('/api/sessions', (req, res) => {
  const files = fs.existsSync(SESSIONS_DIR) ? fs.readdirSync(SESSIONS_DIR) : [];
  const sessions = files
    .filter(f => f.endsWith('.json'))
    .map(f => {
      const data = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, f), 'utf8'));
      return { id: data.id, title: data.title, createdAt: data.createdAt };
    });
  res.json(sessions);
});

// Send message to LLM (uses multi-agent system - sequential, same model)
app.post('/api/chat', async (req, res) => {
  const { sessionId, message } = req.body;

  if (!sessionId) {
    return res.status(400).json({ error: 'Session ID required' });
  }

  try {
    console.log(`\n[API] === Chat Request ===`);
    console.log(`[API] Session: ${sessionId}`);
    console.log(`[API] Player: "${message}"`);

    // Get context
    const context = await contextManager.getFullContext(sessionId);
    console.log(`[API] Context loaded`);

    // Process with orchestrator agent (coordinates all agents)
    const result = await orchestrator.processPlayerInput(context, message);

    console.log(`[API] === Response ===`);
    console.log(`[API] Length: ${result.response?.length} chars`);
    console.log(`[API] Success: ${result.success}`);
    console.log(`[API] Iterations: ${result.iterations}`);

    // Save messages to session WITH TTS DATA
    try {
      const session = contextManager.loadSession(sessionId);
      
      // Add user message
      session.messages.push(
        { role: 'user', content: message, timestamp: new Date().toISOString() }
      );
      
      // Add assistant message WITH TTS dialogue segments
      session.messages.push(
        { 
          role: 'assistant', 
          content: result.response, 
          timestamp: new Date().toISOString(),
          ttsData: {
            segments: result.dialogueSegments || [],
            generatedAt: new Date().toISOString()
          }
        }
      );
      
      contextManager.saveSession(sessionId, session);
      console.log(`[API] Messages saved with TTS data`);
    } catch (e) {
      console.error('[API] Failed to save messages:', e.message);
    }

    // Update context with extracted data (run async, don't wait)
    if (result.contextUpdates) {
      updateSessionContext(sessionId, result.contextUpdates).catch(err => {
        console.error('[API] Context update failed:', err.message);
      });
    }

    res.json({
      response: result.response,
      success: result.success,
      iterations: result.iterations,
      error: result.error || null
    });
  } catch (error) {
    console.error('[API] === ERROR ===');
    console.error('[API]', error.message);
    console.error('[API] Stack:', error.stack);
    res.status(500).json({
      error: 'Failed to process request',
      details: error.message
    });
  }
});

// Update session context (extracted data from orchestrator)
async function updateSessionContext(sessionId, updates) {
  try {
    const session = contextManager.loadSession(sessionId);
    if (!session) return;

    console.log('[Context] Updating session context...');

    // Initialize if needed
    if (!session.context) {
      session.context = { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
    }
    if (!session.journal) {
      session.journal = { summary: '', keyEvents: [], entries: [], characterArcs: {}, locationHistory: [] };
    }

    // Merge characters
    if (updates.characters && updates.characters.length > 0) {
      updates.characters.forEach(char => {
        if (!char.name) return;
        const normalizedName = normalizeCharacterName(char.name);
        const existingIndex = session.context.characters.findIndex(c => 
          normalizeCharacterName(c.name) === normalizedName
        );
        
        if (existingIndex >= 0) {
          session.context.characters[existingIndex] = {
            ...session.context.characters[existingIndex],
            ...char,
            traits: [...new Set([...(session.context.characters[existingIndex].traits || []), ...(char.traits || [])])]
          };
        } else {
          session.context.characters.push({
            name: char.name,
            description: char.description || '',
            traits: char.traits || [],
            status: char.status || 'Active',
            relationships: char.relationships || {}
          });
        }
      });
      console.log('[Context] Characters updated:', session.context.characters.length);
    }

    // Merge locations
    if (updates.locations && updates.locations.length > 0) {
      updates.locations.forEach(loc => {
        if (!loc.name) return;
        const normalizedName = normalizeCharacterName(loc.name);
        const existingIndex = session.context.locations.findIndex(l => 
          normalizeCharacterName(l.name) === normalizedName
        );
        
        if (existingIndex >= 0) {
          session.context.locations[existingIndex] = {
            ...session.context.locations[existingIndex],
            ...loc
          };
        } else {
          session.context.locations.push({
            name: loc.name,
            description: loc.description || '',
            atmosphere: loc.atmosphere || 'Neutral',
            pointsOfInterest: loc.pointsOfInterest || []
          });
        }
      });
      console.log('[Context] Locations updated:', session.context.locations.length);
      
      // Set current location if not set
      if (!session.context.currentLocation && session.context.locations.length > 0) {
        session.context.currentLocation = session.context.locations[0].name;
      }
    }

    // Update inventory
    if (updates.items && updates.items.length > 0) {
      updates.items.forEach(item => {
        if (!item.name) return;
        const normalizedName = normalizeCharacterName(item.name);
        
        if (item.action === 'acquired') {
          const exists = session.context.inventory.some(i => 
            normalizeCharacterName(i.name) === normalizedName
          );
          if (!exists) {
            session.context.inventory.push({
              name: item.name,
              description: item.description || '',
              type: item.type || 'misc',
              effects: item.effects || ''
            });
          }
        } else if (item.action === 'lost' || item.action === 'used') {
          session.context.inventory = session.context.inventory.filter(i => 
            normalizeCharacterName(i.name) !== normalizedName
          );
        }
      });
      console.log('[Context] Inventory updated:', session.context.inventory.length);
    }

    // Update quests
    if (updates.quests && updates.quests.length > 0) {
      updates.quests.forEach(quest => {
        if (!quest.title) return;
        const existingQuest = session.context.quests.find(q => q.title === quest.title);
        if (existingQuest) {
          existingQuest.status = quest.status || existingQuest.status;
          existingQuest.description = quest.description || existingQuest.description;
          existingQuest.objectives = quest.objectives || existingQuest.objectives;
        } else {
          session.context.quests.push({
            title: quest.title,
            description: quest.description || '',
            status: quest.status || 'Active',
            objectives: quest.objectives || []
          });
        }
      });
      console.log('[Context] Quests updated:', session.context.quests.length);
    }

    // Update journal
    if (updates.journal) {
      if (updates.journal.summaryUpdate) {
        session.journal.summary = updates.journal.summaryUpdate;
      }
      if (updates.journal.keyEvent) {
        session.journal.keyEvents.push({
          id: `event_${Date.now()}`,
          timestamp: new Date().toISOString(),
          description: updates.journal.keyEvent
        });
        session.journal.keyEvents = session.journal.keyEvents.slice(-20);
      }
      if (updates.journal.characterArcNotes) {
        Object.entries(updates.journal.characterArcNotes).forEach(([charName, notes]) => {
          session.journal.characterArcs[charName] = {
            ...(session.journal.characterArcs[charName] || {}),
            notes,
            lastUpdated: new Date().toISOString()
          };
        });
      }
      console.log('[Context] Journal updated');
    }

    // Update story state
    if (updates.storyState && Object.keys(updates.storyState).length > 0) {
      session.context.storyState = {
        ...session.context.storyState,
        ...updates.storyState
      };
      console.log('[Context] Story state updated');
    }

    contextManager.saveSession(sessionId, session);
    console.log('[Context] Session saved successfully');

  } catch (error) {
    console.error('[Context] Error:', error.message);
  }
}

// Get session context (all context data for a session)
app.get('/api/session/:id/context', async (req, res) => {
  try {
    const context = await contextManager.getFullContext(req.params.id);
    res.json(context);
  } catch (error) {
    res.status(404).json({ error: 'Session not found' });
  }
});

// Character management (session-specific)
app.post('/api/session/:id/characters', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const { name, description, traits, status, relationships } = req.body;
  
  if (!session.context) {
    session.context = { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
  }
  
  // Check for existing character with same name
  const normalizedName = normalizeCharacterName(name);
  const existingIndex = session.context.characters.findIndex(c => normalizeCharacterName(c.name) === normalizedName);
  
  if (existingIndex >= 0) {
    // Update existing
    session.context.characters[existingIndex] = {
      ...session.context.characters[existingIndex],
      description: description || session.context.characters[existingIndex].description,
      traits: traits || session.context.characters[existingIndex].traits,
      status: status || session.context.characters[existingIndex].status,
      relationships: relationships || session.context.characters[existingIndex].relationships
    };
  } else {
    // Add new
    session.context.characters.push({ 
      name, 
      description: description || '', 
      traits: traits || [], 
      status: status || 'Active',
      relationships: relationships || {}
    });
  }
  
  saveSession(session.id, session);
  res.json(session.context.characters);
});

app.delete('/api/session/:id/characters/:name', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const charName = decodeURIComponent(req.params.name);
  session.context.characters = session.context.characters.filter(c => 
    normalizeCharacterName(c.name) !== normalizeCharacterName(charName)
  );
  saveSession(session.id, session);
  res.json({ success: true });
});

// Location management (session-specific)
app.post('/api/session/:id/locations', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const { name, description, atmosphere, pointsOfInterest } = req.body;
  
  if (!session.context) {
    session.context = { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
  }
  
  const normalizedName = normalizeCharacterName(name);
  const existingIndex = session.context.locations.findIndex(l => normalizeCharacterName(l.name) === normalizedName);
  
  const locationData = { name, description, atmosphere: atmosphere || 'Neutral', pointsOfInterest: pointsOfInterest || [] };
  
  if (existingIndex >= 0) {
    session.context.locations[existingIndex] = { ...session.context.locations[existingIndex], ...locationData };
  } else {
    session.context.locations.push(locationData);
  }
  
  saveSession(session.id, session);
  res.json(session.context.locations);
});

// Set current location
app.post('/api/session/:id/location/:name', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const locName = decodeURIComponent(req.params.name);
  
  if (!session.context) {
    session.context = { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
  }
  
  session.context.currentLocation = locName;
  
  // Add to location history in journal
  if (!session.journal) {
    session.journal = { summary: '', keyEvents: [], entries: [], characterArcs: {}, locationHistory: [] };
  }
  
  session.journal.locationHistory.push({
    name: locName,
    visitedAt: new Date().toLocaleString()
  });
  if (session.journal.locationHistory.length > 20) {
    session.journal.locationHistory = session.journal.locationHistory.slice(-20);
  }
  
  saveSession(session.id, session);
  res.json(session);
});

// Inventory management (session-specific)
app.post('/api/session/:id/inventory', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const { name, description, type, effects } = req.body;
  
  if (!session.context) {
    session.context = { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
  }
  
  const normalizedName = normalizeCharacterName(name);
  const exists = session.context.inventory.some(i => normalizeCharacterName(i.name) === normalizedName);
  
  if (exists) {
    return res.status(400).json({ error: 'Item already in inventory' });
  }
  
  session.context.inventory.push({ name, description: description || '', type: type || 'misc', effects: effects || '' });
  saveSession(session.id, session);
  res.json(session.context.inventory);
});

app.delete('/api/session/:id/inventory/:name', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  session.context.inventory = session.context.inventory.filter(i => 
    normalizeCharacterName(i.name) !== normalizeCharacterName(req.params.name)
  );
  saveSession(session.id, session);
  res.json({ success: true });
});

// Quest management (session-specific)
app.post('/api/session/:id/quests', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const { title, description, status, objectives } = req.body;
  
  if (!session.context) {
    session.context = { characters: [], locations: [], inventory: [], quests: [], storyState: {} };
  }
  
  session.context.quests.push({ title, description, status: status || 'Active', objectives: objectives || [] });
  saveSession(session.id, session);
  res.json(session.context.quests);
});

app.put('/api/session/:id/quests/:title', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  const quest = session.context?.quests?.find(q => q.title === req.params.title);
  if (quest) {
    quest.status = req.body.status || quest.status;
    quest.description = req.body.description || quest.description;
    quest.objectives = req.body.objectives || quest.objectives;
    saveSession(session.id, session);
    res.json(quest);
  } else {
    res.status(404).json({ error: 'Quest not found' });
  }
});

// Update session settings (including character data)
app.put('/api/session/:id', (req, res) => {
  const session = loadSession(req.params.id);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }

  const { title, systemPrompt, character, context, journal } = req.body;
  if (title) session.title = title;
  if (systemPrompt) session.systemPrompt = systemPrompt;
  if (character) session.character = character;
  if (context) session.context = context;
  if (journal) session.journal = journal;

  session.updatedAt = new Date().toISOString();
  saveSession(session.id, session);
  res.json(session);
});

// Delete session
app.delete('/api/session/:id', (req, res) => {
  const filePath = path.join(SESSIONS_DIR, `${req.params.id}.json`);
  if (fs.existsSync(filePath)) {
    fs.unlinkSync(filePath);
    res.json({ success: true });
  } else {
    res.status(404).json({ error: 'Session not found' });
  }
});

// Clear context for session (reset to fresh state)
app.post('/api/session/:id/clear-context', async (req, res) => {
  try {
    const { keepMessages } = req.body;
    const session = contextManager.loadSession(req.params.id);
    if (!session) {
      return res.status(404).json({ error: 'Session not found' });
    }
    
    session.context = {
      characters: [],
      locations: [],
      inventory: [],
      quests: [],
      storyState: {},
      currentLocation: null
    };
    
    session.journal = {
      summary: '',
      keyEvents: [],
      entries: [],
      characterArcs: {},
      locationHistory: []
    };
    
    if (!keepMessages) {
      session.messages = [];
    }
    
    contextManager.saveSession(req.params.id, session);
    res.json(session);
  } catch (error) {
    res.status(404).json({ error: 'Session not found' });
  }
});

app.listen(PORT, () => {
  console.log(`\n🎮 AI RPG Server running on http://localhost:${PORT}`);
  console.log(`🤖 LM Studio should be running on ${LM_STUDIO_URL}`);
  console.log(`📁 Sessions stored in: ${SESSIONS_DIR}\n`);

  // Test LM Studio connection
  testLMStudioConnection();
  
  // Test TTS service health
  testTTSService();
});

// Test TTS service on startup
async function testTTSService() {
  try {
    const health = await audioGenerator.healthCheck();
    if (health.healthy) {
      console.log(`🔊 TTS Service: OK (${health.voiceCount} voices available)`);
    } else {
      console.log(`🔊 TTS Service: Not configured (${health.reason})`);
      console.log(`   Set AZURE_SPEECH_KEY and AZURE_SPEECH_REGION environment variables`);
    }
  } catch (error) {
    console.log(`🔊 TTS Service: Not configured`);
  }
}

// ============ TTS ENDPOINTS ============

// Get TTS service health status
app.get('/api/tts/status', async (req, res) => {
  try {
    const health = await audioGenerator.healthCheck();
    res.json({
      available: health.healthy,
      voiceCount: health.voiceCount || 0,
      reason: health.reason
    });
  } catch (error) {
    res.json({ available: false, reason: error.message });
  }
});

// Get session TTS settings
app.get('/api/session/:id/tts/settings', (req, res) => {
  const settings = ttsSettings.get(req.params.id) || {
    enabled: true,
    autoPlay: false,
    narratorVoice: { name: 'en-US-GuyNeural', displayName: 'Guy (Narrator)', gender: 'Male' },
    volume: 1,
    speed: 1
  };
  res.json(settings);
});

// Update session TTS settings
app.post('/api/session/:id/tts/settings', (req, res) => {
  const { enabled, autoPlay, narratorVoice, volume, speed } = req.body;
  const currentSettings = ttsSettings.get(req.params.id) || {
    enabled: true,
    autoPlay: false,
    narratorVoice: { name: 'en-US-GuyNeural', displayName: 'Guy (Narrator)', gender: 'Male' },
    volume: 1,
    speed: 1
  };
  
  const newSettings = {
    ...currentSettings,
    ...(enabled !== undefined && { enabled }),
    ...(autoPlay !== undefined && { autoPlay }),
    ...(narratorVoice !== undefined && { narratorVoice }),
    ...(volume !== undefined && { volume }),
    ...(speed !== undefined && { speed })
  };
  
  ttsSettings.set(req.params.id, newSettings);
  res.json(newSettings);
});

// Generate audio for narration
app.post('/api/tts/generate', async (req, res) => {
  try {
    const { narration, characters, narratorVoice } = req.body;

    if (!narration) {
      return res.status(400).json({ error: 'Narration text required' });
    }

    console.log('[TTS API] Generate request received');
    console.log('[TTS API] Narration length:', narration.length);
    console.log('[TTS API] Characters:', characters?.length || 0);

    const result = await audioGenerator.generateAudio(narration, characters || [], narratorVoice);

    if (result.success) {
      res.json(result.audioData);
    } else {
      res.status(500).json({ error: result.error });
    }
  } catch (error) {
    console.error('[TTS API] Error:', error.message);
    res.status(500).json({ error: 'TTS generation failed', details: error.message });
  }
});

// Generate simple audio (single segment)
app.post('/api/tts/generate/simple', async (req, res) => {
  try {
    const { text, voiceConfig } = req.body;

    if (!text) {
      return res.status(400).json({ error: 'Text required' });
    }

    const result = await audioGenerator.generateSimpleAudio(text, voiceConfig);

    if (result.success) {
      res.json(result.audioData);
    } else {
      res.status(500).json({ error: result.error });
    }
  } catch (error) {
    console.error('[TTS API] Error:', error.message);
    res.status(500).json({ error: 'TTS generation failed', details: error.message });
  }
});

// Assign voice to character
app.post('/api/session/:id/characters/:name/voice', (req, res) => {
  try {
    const { voice } = req.body;
    const session = contextManager.loadSession(req.params.id);
    
    if (!session) {
      return res.status(404).json({ error: 'Session not found' });
    }

    const character = session.context?.characters?.find(c => c.name === req.params.name);
    if (!character) {
      return res.status(404).json({ error: 'Character not found' });
    }

    character.voice = voice;
    contextManager.saveSession(req.params.id, session);
    
    res.json({ success: true, character });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Auto-assign voices to all characters in session
app.post('/api/session/:id/characters/auto-voice', (req, res) => {
  try {
    const session = contextManager.loadSession(req.params.id);
    
    if (!session) {
      return res.status(404).json({ error: 'Session not found' });
    }

    if (!session.context?.characters) {
      return res.json({ characters: [] });
    }

    // Import voice assignment function
    const { assignCharacterVoice } = require('../../client/src/data/voicePresets');
    
    session.context.characters.forEach(char => {
      if (!char.voice) {
        const assignedVoice = assignCharacterVoice(char);
        char.voice = assignedVoice;
      }
    });

    contextManager.saveSession(req.params.id, session);
    res.json({ characters: session.context.characters });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Test LM Studio connection on startup
async function testLMStudioConnection() {
  try {
    console.log('[Startup] Testing LM Studio connection...');
    const response = await axios.get(`${LM_STUDIO_URL}/v1/models`, { timeout: 5000 });
    console.log('[Startup] ✓ LM Studio is accessible');
    if (response.data.data && response.data.data.length > 0) {
      console.log(`[Startup] ✓ Models available: ${response.data.data.length}`);
    }
  } catch (error) {
    console.error('[Startup] ✗ LM Studio is NOT accessible');
    console.error('[Startup] Please make sure LM Studio is running on port 1234');
    console.error('[Startup] Error:', error.message);
  }
}
