const express = require('express');
const cron = require('node-cron');
const fs = require('fs');
const http = require('http');
const https = require('https');
const path = require('path');
const cheerio = require('cheerio');
const Redis = require('ioredis');

const redisHost = process.env.REDIS_HOST || 'redis.platform-data.svc.cluster.local';
const redis = new Redis({ host: redisHost, port: 6379, maxRetriesPerRequest: 1 });

async function getGroupUrl(type = 'trip-group-url') {
  try {
    const raw = await redis.get('whatsapp_config');
    if (raw) {
      const data = JSON.parse(raw);
      if (data[type]) return data[type];
    }
  } catch (e) {
    console.error('[Redis] Failed to fetch config:', e.message);
    return process.env.GROUP_URL;
  }
  return process.env.GROUP_URL;
}

const app = express();
app.use(express.json({ limit: '1mb' }));

const DATA_FILE = process.env.DATA_FILE || '/data/trips.json';
const GATEWAY_URL = process.env.GATEWAY_URL || 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/send-message';
const OLLAMA_URL = process.env.OLLAMA_URL || 'http://ollama.ai-agents.svc.cluster.local:11434/api/generate';
const PERSONAL_NUM = process.env.PERSONAL_NUM;
const GROUP_URL = process.env.GROUP_URL;
const PORT = parseInt(process.env.PORT) || 6002;

// ── Persistence ──────────────────────────────────────────────────────────────
function loadTrips() {
  try { return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); }
  catch { return []; }
}

function saveTrips(trips) {
  fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });
  fs.writeFileSync(DATA_FILE, JSON.stringify(trips, null, 2));
}

// ── HTTP Helpers ─────────────────────────────────────────────────────────────
function fetchJson(url) {
  return new Promise((resolve, reject) => {
    const lib = url.startsWith('https') ? https : http;
    lib.get(url, { headers: { 'User-Agent': 'AgenticOS-TripService/1.0' } }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
      });
    }).on('error', reject);
  });
}

function fetchText(url) {
  return new Promise((resolve, reject) => {
    const lib = url.startsWith('https') ? https : http;
    lib.get(url, { headers: { 'User-Agent': 'AgenticOS-TripService/1.0' } }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => resolve(data));
    }).on('error', reject);
  });
}

// ── Integrations ─────────────────────────────────────────────────────────────
async function getWeather(location) {
  try {
    const data = await fetchJson(`https://wttr.in/${encodeURIComponent(location)}?format=j1`);
    const curr = data.current_condition[0];
    return `${curr.temp_F}°F, ${curr.weatherDesc[0].value}`;
  } catch (e) {
    return 'Weather unavailable';
  }
}

async function scrapeNpsConditions(parkCode) {
  if (!parkCode) return null;
  const url = `https://www.nps.gov/${parkCode}/planyourvisit/conditions.htm`;
  try {
    const html = await fetchText(url);
    const $ = cheerio.load(html);
    const alerts = [];
    $('.Alert-title').each((i, el) => {
      alerts.push(`⚠️ ${$(el).text().trim()}`);
    });
    if (alerts.length > 0) {
      return alerts.slice(0, 5).join('\n');
    }
    return 'No active NPS conditions listed.';
  } catch (e) {
    console.error('[TripService] NPS scrape error:', e.message);
    return 'NPS conditions unavailable';
  }
}

async function getWeatherAlerts(stateCode) {
  try {
    const data = await fetchJson(`https://api.weather.gov/alerts/active?area=${stateCode}`);
    if (data.features && data.features.length > 0) {
      return data.features.map(f => `🌩️ ${f.properties.event || f.properties.headline}`).slice(0, 3).join('\n');
    }
    return 'No active weather alerts.';
  } catch (e) {
    console.error('[TripService] Weather alerts error:', e.message);
    return 'Weather alerts unavailable';
  }
}

async function getTdotTraffic() {
  try {
    // Basic TDOT Incidents fetch (Smoky Mountains general area filter could be applied, returning raw count for brevity)
    const data = await fetchJson('https://smartway.tn.gov/Traffic/Api/Incidents');
    if (data && data.length >= 0) {
      return `Current active incidents in TN: ${data.length}`;
    }
    return 'No traffic data.';
  } catch (e) {
    return 'Traffic data unavailable';
  }
}

async function getAiInsight(activityDetails, location, trip) {
  return new Promise((resolve) => {
    const prompt = `Provide one short, clever tip or insight for someone visiting ${location} today, doing: ${activityDetails}. Keep it under 2 sentences.`;
    
    if (trip && trip.llmProvider === 'anthropic') {
      const modelName = trip.llmModel || 'claude-3-5-sonnet-20240620';
      const body = JSON.stringify({
        model: modelName,
        max_tokens: 150,
        messages: [{ role: 'user', content: prompt }]
      });
      
      const req = https.request({
        hostname: 'api.anthropic.com',
        path: '/v1/messages',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.ANTHROPIC_API_KEY,
          'anthropic-version': '2023-06-01',
          'Content-Length': Buffer.byteLength(body)
        }
      }, (res) => {
        let d = '';
        res.on('data', c => d += c);
        res.on('end', () => {
          try {
            const data = JSON.parse(d);
            resolve(data.content[0].text.trim());
          } catch { resolve('Have a great trip!'); }
        });
      });
      req.on('error', () => resolve('Have a great trip!'));
      req.write(body);
      req.end();

    } else {
      // Default to Ollama
      const modelName = trip ? (trip.llmModel || 'qwen2.5:0.5b') : 'qwen2.5:0.5b';
      const body = JSON.stringify({ model: modelName, prompt, stream: false });
      const url = new URL(OLLAMA_URL);
      const req = http.request({
        hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
      }, (res) => {
        let d = '';
        res.on('data', c => d += c);
        res.on('end', () => {
          try { resolve(JSON.parse(d).response.trim()); } catch { resolve('Have a great trip!'); }
        });
      });
      req.on('error', () => resolve('Have a great trip!'));
      req.write(body);
      req.end();
    }
  });
}

async function generateAiSchedule(activities, meals, date, trip) {
  return new Promise((resolve) => {
    const prompt = `You are a strict JSON API scheduling agent. Extract a daily schedule from these plans:
Activities: ${activities}
Meals: ${meals}
Date: ${date}

Return ONLY a JSON array of objects. Each object must have "time" (in 24-hour HH:mm format) and "message" (a short, fun 1-sentence reminder). Estimate reasonable times if none are given (e.g. breakfast 8:00, lunch 12:30, dinner 18:30). DO NOT output any markdown or conversational text.`;

    const handleResponse = (d) => {
      try {
        const raw = d;
        const match = raw.match(/\[[\s\S]*\]/);
        if (match) {
          resolve(JSON.parse(match[0]));
        } else {
          resolve([]);
        }
      } catch (e) {
        console.error('[TripService] AI Schedule parsing failed:', e.message);
        resolve([]);
      }
    };

    if (trip && trip.llmProvider === 'anthropic') {
      const modelName = trip.llmModel || 'claude-3-5-sonnet-20240620';
      const body = JSON.stringify({
        model: modelName,
        max_tokens: 300,
        messages: [{ role: 'user', content: prompt }]
      });
      const req = https.request({
        hostname: 'api.anthropic.com',
        path: '/v1/messages',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.ANTHROPIC_API_KEY,
          'anthropic-version': '2023-06-01',
          'Content-Length': Buffer.byteLength(body)
        }
      }, (res) => {
        let d = '';
        res.on('data', c => d += c);
        res.on('end', () => {
          try {
            const data = JSON.parse(d);
            handleResponse(data.content[0].text);
          } catch { resolve([]); }
        });
      });
      req.on('error', () => resolve([]));
      req.write(body);
      req.end();

    } else {
      // Default to Ollama
      const modelName = trip.llmModel || 'qwen2.5:0.5b';
      const body = JSON.stringify({ model: modelName, prompt, stream: false });
      const url = new URL(OLLAMA_URL);
      const req = http.request({
        hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
      }, (res) => {
        let d = '';
        res.on('data', c => d += c);
        res.on('end', () => {
          try {
            handleResponse(JSON.parse(d).response);
          } catch { resolve([]); }
        });
      });
      req.on('error', () => resolve([]));
      req.write(body);
      req.end();
    }
  });
}

function scheduleRemindersBatch(events, trip, dateStr) {
  if (!events || events.length === 0) return;
  
  // Base date object for today in Eastern Time (approximated locally)
  const baseDate = new Date(dateStr + "T00:00:00-04:00"); 

  const items = events.map(ev => {
    const [hh, mm] = ev.time.split(':');
    const eventTime = new Date(baseDate);
    eventTime.setHours(parseInt(hh, 10));
    eventTime.setMinutes(parseInt(mm, 10));
    
    // Send reminder 15 minutes before the event time
    const sendAt = new Date(eventTime.getTime() - 15 * 60000);
    
    // Don't schedule in the past
    if (sendAt <= new Date()) return null;

    return {
      message: `⏰ *${trip.name} Schedule Alert*\n\n${ev.message}\n\n_Starts at ${ev.time}_`,
      sendAt: sendAt.toISOString(),
      label: `${trip.name} - ${ev.message.slice(0, 30)}...`,
      to: trip.sendToGroup && trip.groupUrls && trip.groupUrls.length > 0 ? trip.groupUrls[0] : null // handled in batch
    };
  }).filter(Boolean);

  if (items.length === 0) return;

  // Resolve group URL for items that need it
  getGroupUrl('trip-group-url').then(groupUrl => {
    items.forEach(item => {
      if (!item.to) item.to = groupUrl || PERSONAL_NUM;
    });

    const body = JSON.stringify({ items });
    const req = http.request({
    hostname: 'reminder-service.ai-agents.svc.cluster.local',
    port: 6001,
    path: '/reminders/batch',
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
  }, (res) => {
    res.on('data', () => {});
    res.on('end', () => console.log(`[TripService] Batch queued ${items.length} reminders via reminder-service`));
  });
  req.on('error', (e) => console.error('[TripService] Failed to queue reminders:', e.message));
  req.write(body);
  req.end();
}

function sendWhatsApp(to, message) {
  return new Promise((resolve) => {
    const body = JSON.stringify({ to, message });
    const url = new URL(GATEWAY_URL);
    const req = http.request({
      hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
    }, (res) => {
      res.on('data', () => {});
      res.on('end', () => resolve(true));
    });
    req.on('error', () => resolve(false));
    req.write(body);
    req.end();
  });
}

// ── Briefing Generation ──────────────────────────────────────────────────────
async function generateAndSendBriefing(trip, dayPlan) {
  const weather = await getWeather(dayPlan.location || 'Pigeon Forge, TN');
  const weatherAlerts = await getWeatherAlerts('TN');
  const nps = await scrapeNpsConditions(trip.npsParkCode);
  const traffic = await getTdotTraffic();
  const insight = await getAiInsight(dayPlan.activities, dayPlan.location || 'Smoky Mountains', trip);

  const message = `*🏕️ ${trip.name} - ${dayPlan.date} Briefing*\n\n` +
    `*🌤️ Weather (${dayPlan.location || 'Pigeon Forge'}):* ${weather}\n\n` +
    `*🌩️ Weather Alerts (TN):*\n${weatherAlerts}\n\n` +
    `*📋 Today's Plan:*\n${dayPlan.activities}\n\n` +
    `*🍴 Meals:*\n${dayPlan.meals}\n\n` +
    (nps ? `*🌲 Park Conditions:*\n${nps}\n\n` : '') +
    `*🚗 Traffic Info:*\n${traffic}\n\n` +
    `*🤖 AI Insight:*\n${insight}`;

  console.log(`[TripService] Sending briefing for ${trip.name} - ${dayPlan.date}`);
  await sendWhatsApp(PERSONAL_NUM, message);
  
  const tripGroupUrl = await getGroupUrl('trip-group-url');
  
  // Support specific groups for this trip, fallback to default if configured
  let groupsToNotify = [];
  if (trip.groupUrls && Array.isArray(trip.groupUrls) && trip.groupUrls.length > 0) {
    groupsToNotify = trip.groupUrls;
  } else if (trip.sendToGroup && tripGroupUrl) {
    groupsToNotify = [tripGroupUrl];
  }
  
  for (const group of groupsToNotify) {
    await sendWhatsApp(group, message);
  }

  // Generate and schedule detailed AI itinerary
  console.log(`[TripService] Generating detailed AI schedule for ${trip.name} - ${dayPlan.date}`);
  const scheduleEvents = await generateAiSchedule(dayPlan.activities, dayPlan.meals, dayPlan.date, trip);
  if (scheduleEvents.length > 0) {
    scheduleRemindersBatch(scheduleEvents, trip, dayPlan.date);
  } else {
    console.log(`[TripService] AI yielded no parseable schedule events.`);
  }
}

// ── Routes ───────────────────────────────────────────────────────────────────
app.post('/trips', (req, res) => {
  const trip = req.body;
  if (!trip.name || !trip.days) return res.status(400).json({ error: 'Missing name or days' });
  
  trip.id = `trip_${Date.now()}`;
  const trips = loadTrips();
  trips.push(trip);
  saveTrips(trips);
  
  res.json({ success: true, id: trip.id });
});

app.get('/trips', (req, res) => {
  res.json(loadTrips());
});

app.put('/trips/:id', (req, res) => {
  const trips = loadTrips();
  const index = trips.findIndex(t => t.id === req.params.id);
  if (index === -1) return res.status(404).json({ error: 'Trip not found' });
  
  const updatedTrip = { ...req.body, id: req.params.id }; // preserve ID
  trips[index] = updatedTrip;
  saveTrips(trips);
  res.json({ success: true });
});

app.delete('/trips/:id', (req, res) => {
  let trips = loadTrips();
  trips = trips.filter(t => t.id !== req.params.id);
  saveTrips(trips);
  res.json({ success: true });
});

app.post('/trips/:id/test', async (req, res) => {
  const trips = loadTrips();
  const trip = trips.find(t => t.id === req.params.id);
  if (!trip) return res.status(404).json({ error: 'Trip not found' });
  
  // Just use the first day's plan for the test
  const dayPlan = trip.days[0];
  try {
    await generateAndSendBriefing(trip, dayPlan);
    res.json({ success: true, message: 'Test briefing sent!' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get('/health', (req, res) => res.json({ status: 'ok' }));

// ── Scheduler (Runs every minute, checks if time to send) ────────────────────
cron.schedule('* * * * *', async () => {
  const now = new Date();
  const trips = loadTrips();
  
  // Format current date as YYYY-MM-DD
  const tzOffset = -4 * 60; // Assuming EDT for now, ideally use a robust timezone approach
  const localTime = new Date(now.getTime() + tzOffset * 60000);
  const todayStr = localTime.toISOString().split('T')[0];
  const currentHour = localTime.getUTCHours().toString().padStart(2, '0');
  const currentMin = localTime.getUTCMinutes().toString().padStart(2, '0');
  const currentTime = `${currentHour}:${currentMin}`;

  for (const trip of trips) {
    const briefingTime = trip.briefingTime || '07:00';
    if (currentTime !== briefingTime) continue; // Only trigger at exact minute

    const dayPlan = trip.days.find(d => d.date === todayStr);
    if (!dayPlan) continue;

    // To prevent duplicate sends if cron runs multiple times in the same minute
    if (dayPlan.sent) continue;

    try {
      await generateAndSendBriefing(trip, dayPlan);
      dayPlan.sent = true;
      saveTrips(trips);
    } catch (err) {
      console.error(`[TripService] Failed to send briefing: ${err.message}`);
    }
  }
});

app.listen(PORT, () => {
  console.log(`[TripService] Listening on :${PORT}`);
});
