/**
 * Blog Self-Learning Loop (SLL v1.0)
 *
 * Routes:
 *   POST /api/blog/:id/performance    — log engagement metrics for a post
 *   GET  /api/blog/sll/insights       — current learning state
 *   POST /api/blog/sll/analyze        — trigger LLM pattern extraction
 *   GET  /api/blog/sll/patterns       — all learned patterns
 *   GET  /api/blog/sll/posting-time   — best posting time (Umami + SLL combined)
 *   POST /api/blog/sll/sync-umami     — refresh Umami analytics cache
 */

import { Router, Request, Response } from "express";
import { pool } from "../db/client";

export const blogSllRouter = Router();

// ─────────────────────────────────────────────────────────────────
// POST /api/blog/:id/performance — log LinkedIn engagement
// ─────────────────────────────────────────────────────────────────
blogSllRouter.post("/:id/performance", async (req: Request, res: Response) => {
  const { id } = req.params;
  const {
    platform = "linkedin",
    impressions,
    comments = 0,
    shares = 0,
    saves = 0,
    likes = 0,
    hook_text,
    posted_at,
    notes,
  } = req.body as {
    platform?: string;
    impressions?: number;
    comments?: number;
    shares?: number;
    saves?: number;
    likes?: number;
    hook_text?: string;
    posted_at?: string;
    notes?: string;
  };

  try {
    // Pull blog metadata for pattern context
    const blogRow = await pool.query(
      `SELECT topic, pipeline_version, word_count, linkedin_post FROM blog_drafts WHERE id = $1::uuid`,
      [id]
    );
    if (blogRow.rows.length === 0) {
      res.status(404).json({ success: false, error: "Blog draft not found" });
      return;
    }
    const blog = blogRow.rows[0];

    // Auto-extract hook from linkedin_post if not provided
    const resolvedHook = hook_text ||
      (blog.linkedin_post ? blog.linkedin_post.split("\n")[0].slice(0, 120) : null);

    const result = await pool.query(
      `INSERT INTO blog_performance
         (blog_id, platform, impressions, comments, shares, saves, likes,
          hook_text, blog_type, topic, word_count, pipeline_version, posted_at, notes)
       VALUES ($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
       RETURNING id, engagement_score`,
      [
        id, platform, impressions ?? null, comments, shares, saves, likes,
        resolvedHook, blog.topic, blog.topic,
        blog.word_count, blog.pipeline_version,
        posted_at ? new Date(posted_at) : null, notes ?? null,
      ]
    );

    const row = result.rows[0];
    const score = row.engagement_score;
    const tier = score >= 20 ? "gold" : score >= 10 ? "silver" : score >= 4 ? "bronze" : "miss";

    console.log(`SLL: Performance logged for ${id} — score ${score} (${tier})`);

    res.json({
      success: true,
      performance_id: row.id,
      engagement_score: score,
      tier,
      formula: `(comments×3) + (shares×2) + (saves×2) = ${comments * 3} + ${shares * 2} + ${saves * 2}`,
    });
  } catch (err) {
    console.error("SLL performance log error:", err);
    res.status(500).json({ success: false, error: String(err) });
  }
});

// ─────────────────────────────────────────────────────────────────
// GET /api/blog/sll/insights — current learning state
// ─────────────────────────────────────────────────────────────────
blogSllRouter.get("/sll/insights", async (_req: Request, res: Response) => {
  try {
    const [stateRes, statsRes, topRes, bottomRes, patternsRes] = await Promise.all([
      // Latest SLL state snapshot
      pool.query(
        `SELECT * FROM blog_sll_state ORDER BY week_start DESC LIMIT 1`
      ),
      // Overall performance stats
      pool.query(`
        SELECT
          COUNT(*) as total_posts,
          AVG(engagement_score) as avg_score,
          MAX(engagement_score) as best_score,
          COUNT(*) FILTER (WHERE engagement_score >= 20) as gold_count,
          COUNT(*) FILTER (WHERE engagement_score >= 10) as silver_count,
          COUNT(*) FILTER (WHERE engagement_score >= 4)  as bronze_count,
          COUNT(*) FILTER (WHERE engagement_score < 4)   as miss_count
        FROM blog_performance
      `),
      // Top 5 posts
      pool.query(`
        SELECT d.title, d.topic, p.engagement_score, p.hook_text,
               p.word_count, p.blog_type, p.posted_at,
               CASE WHEN p.engagement_score >= 20 THEN 'gold'
                    WHEN p.engagement_score >= 10 THEN 'silver'
                    WHEN p.engagement_score >= 4  THEN 'bronze'
                    ELSE 'miss' END as tier
        FROM blog_performance p
        JOIN blog_drafts d ON d.id = p.blog_id
        ORDER BY p.engagement_score DESC LIMIT 5
      `),
      // Bottom 5 posts
      pool.query(`
        SELECT d.title, d.topic, p.engagement_score, p.hook_text, p.word_count
        FROM blog_performance p
        JOIN blog_drafts d ON d.id = p.blog_id
        WHERE p.engagement_score IS NOT NULL
        ORDER BY p.engagement_score ASC LIMIT 5
      `),
      // Active learned patterns
      pool.query(`
        SELECT pattern_type, pattern_value, performance_class, avg_engagement, sample_count
        FROM blog_learned_patterns
        WHERE active = TRUE
        ORDER BY performance_class, avg_engagement DESC NULLS LAST
      `),
    ]);

    const state = stateRes.rows[0] || null;
    const stats = statsRes.rows[0];

    res.json({
      success: true,
      stats: {
        total_posts: Number(stats.total_posts),
        avg_score: stats.avg_score ? Math.round(Number(stats.avg_score) * 10) / 10 : null,
        best_score: Number(stats.best_score) || 0,
        tiers: {
          gold: Number(stats.gold_count),
          silver: Number(stats.silver_count),
          bronze: Number(stats.bronze_count),
          miss: Number(stats.miss_count),
        },
      },
      current_state: state,
      top_posts: topRes.rows,
      bottom_posts: bottomRes.rows,
      learned_patterns: {
        winners: patternsRes.rows.filter((p: any) => p.performance_class === "winner"),
        losers: patternsRes.rows.filter((p: any) => p.performance_class === "loser"),
      },
      sll_ready: Number(stats.total_posts) >= 5,
      note: Number(stats.total_posts) < 5
        ? `SLL needs ${5 - Number(stats.total_posts)} more posts with performance data before pattern extraction`
        : "SLL active — enough data for pattern extraction",
    });
  } catch (err) {
    res.status(500).json({ success: false, error: String(err) });
  }
});

// ─────────────────────────────────────────────────────────────────
// In-memory Umami cache (TTL 1h — single PM2 process)
// ─────────────────────────────────────────────────────────────────
interface UmamiSlot { weekday: number; hour: number; sessions: number }
let umamiCache: { slots: UmamiSlot[]; fetchedAt: number } | null = null;
const UMAMI_TTL_MS = 60 * 60 * 1000; // 1h

const UMAMI_URL      = process.env["UMAMI_URL"]        ?? "https://analytics.fichtmueller.org";
const UMAMI_USER     = process.env["UMAMI_USER"]       ?? "admin";
const UMAMI_PASS     = process.env["UMAMI_PASS"]       ?? "";
const UMAMI_WEBSITE  = process.env["UMAMI_WEBSITE_ID"] ?? "c737bf75-ccc4-463b-992a-13bed31d7f43";

const DAY_NAMES = ["Mo","Di","Mi","Do","Fr","Sa","So"];

async function fetchUmamiToken(): Promise<string | null> {
  try {
    const r = await fetch(`${UMAMI_URL}/api/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username: UMAMI_USER, password: UMAMI_PASS }),
      signal: AbortSignal.timeout(8000),
    });
    const d = await r.json() as { token?: string };
    return d.token ?? null;
  } catch { return null; }
}

async function fetchUmamiSlots(): Promise<UmamiSlot[]> {
  // Return cache if fresh
  if (umamiCache && Date.now() - umamiCache.fetchedAt < UMAMI_TTL_MS) {
    return umamiCache.slots;
  }

  const token = await fetchUmamiToken();
  if (!token) return [];

  const startAt = Date.now() - 90 * 24 * 60 * 60 * 1000;
  const endAt   = Date.now();
  const url = `${UMAMI_URL}/api/websites/${UMAMI_WEBSITE}/sessions?startAt=${startAt}&endAt=${endAt}&pageSize=500&page=1`;

  try {
    const r = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
      signal: AbortSignal.timeout(15000),
    });
    const d = await r.json() as { data?: Array<{ firstAt?: string }> };
    const sessions = d.data ?? [];

    // Build (weekday, hour) counts — Berlin = UTC+2 in April/summer
    const counts: Record<string, number> = {};
    for (const s of sessions) {
      if (!s.firstAt) continue;
      const dt = new Date(s.firstAt);
      const berlinH = (dt.getUTCHours() + 2) % 24;
      const berlinWd = dt.getUTCDay() === 0 ? 6 : dt.getUTCDay() - 1; // 0=Mon
      const key = `${berlinWd}:${berlinH}`;
      counts[key] = (counts[key] ?? 0) + 1;
    }

    const slots: UmamiSlot[] = Object.entries(counts).map(([key, n]) => {
      const [wd, h] = key.split(":").map(Number);
      return { weekday: wd, hour: h, sessions: n };
    });

    umamiCache = { slots, fetchedAt: Date.now() };
    return slots;
  } catch { return []; }
}

// ─────────────────────────────────────────────────────────────────
// GET /api/blog/sll/posting-time — best posting time
// Combines Umami traffic data + SLL historical engagement by slot
// ─────────────────────────────────────────────────────────────────
blogSllRouter.get("/sll/posting-time", async (_req: Request, res: Response) => {
  try {
    // 1. Umami: traffic per (weekday, hour)
    const umamiSlots = await fetchUmamiSlots();
    const umamiMax = Math.max(1, ...umamiSlots.map((s) => s.sessions));

    // 2. SLL: avg engagement per (weekday, hour) from historical posts
    const sllRes = await pool.query(`
      SELECT
        EXTRACT(DOW FROM posted_at AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Berlin')::int AS wd_raw,
        EXTRACT(HOUR FROM posted_at AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Berlin')::int AS hour,
        AVG(engagement_score) AS avg_eng,
        MAX(engagement_score) AS best_eng,
        COUNT(*) AS post_count
      FROM blog_performance
      WHERE posted_at IS NOT NULL AND engagement_score IS NOT NULL
      GROUP BY wd_raw, hour
    `);

    // Convert Sunday=0 (PostgreSQL DOW) to Monday=0 index
    const sllMap: Record<string, { avgEng: number; bestEng: number; count: number }> = {};
    for (const row of sllRes.rows) {
      const wd = row.wd_raw === 0 ? 6 : Number(row.wd_raw) - 1; // Mon=0
      const key = `${wd}:${row.hour}`;
      sllMap[key] = {
        avgEng:  Math.round(Number(row.avg_eng)  * 10) / 10,
        bestEng: Number(row.best_eng),
        count:   Number(row.post_count),
      };
    }
    const sllMax = Math.max(1, ...Object.values(sllMap).map((v) => v.avgEng));

    // 3. Build candidate slots (union of Umami + SLL slots)
    const allKeys = new Set([
      ...umamiSlots.map((s) => `${s.weekday}:${s.hour}`),
      ...Object.keys(sllMap),
    ]);

    const scored = Array.from(allKeys).map((key) => {
      const [wd, h] = key.split(":").map(Number);
      const umami   = umamiSlots.find((s) => s.weekday === wd && s.hour === h);
      const sll     = sllMap[key];

      const umamiScore = umami ? umami.sessions / umamiMax : 0;
      const sllScore   = sll   ? sll.avgEng   / sllMax   : 0;

      // Weight: 50% Umami traffic + 50% SLL engagement
      // If no SLL data → 100% Umami; if no Umami → 100% SLL
      const hasUmami = !!umami;
      const hasSll   = !!sll;
      let combined: number;
      if (hasUmami && hasSll) {
        combined = umamiScore * 0.5 + sllScore * 0.5;
      } else if (hasUmami) {
        combined = umamiScore * 0.7; // penalise slots with no SLL validation
      } else {
        combined = sllScore * 0.6;   // SLL-only slots get a slight boost
      }

      return {
        weekday: wd,
        hour: h,
        label: `${DAY_NAMES[wd]} ${String(h).padStart(2, "0")}:00h`,
        score: Math.round(combined * 100),
        umami_sessions: umami?.sessions ?? 0,
        sll_avg_engagement: sll?.avgEng ?? null,
        sll_best_engagement: sll?.bestEng ?? null,
        sll_post_count: sll?.count ?? 0,
        data_sources: [hasUmami ? "umami" : null, hasSll ? "sll" : null].filter(Boolean),
      };
    });

    // Sort by score descending
    scored.sort((a, b) => b.score - a.score);
    const top = scored.slice(0, 10);

    // Build weekday summary (best hour per weekday)
    const byWeekday: Record<number, typeof top[0]> = {};
    for (const slot of scored) {
      if (!byWeekday[slot.weekday] || slot.score > byWeekday[slot.weekday].score) {
        byWeekday[slot.weekday] = slot;
      }
    }
    const weekdaySummary = DAY_NAMES.map((name, wd) => ({
      weekday: wd,
      name,
      best_slot: byWeekday[wd] ?? null,
    }));

    res.json({
      success: true,
      top_slots: top,
      weekday_summary: weekdaySummary,
      recommended: top[0] ?? null,
      data_sources: {
        umami_sessions_analyzed: umamiSlots.reduce((s, x) => s + x.sessions, 0),
        umami_cache_age_min:     umamiCache ? Math.round((Date.now() - umamiCache.fetchedAt) / 60000) : null,
        sll_posts_with_time:     sllRes.rows.length,
      },
      note: sllRes.rows.length === 0
        ? "SLL has no timed posts yet — using Umami traffic data only"
        : `Combined Umami + ${sllRes.rows.length} SLL engagement data point(s)`,
    });
  } catch (err) {
    console.error("posting-time error:", err);
    res.status(500).json({ success: false, error: String(err) });
  }
});

// ─────────────────────────────────────────────────────────────────
// POST /api/blog/sll/sync-umami — force-refresh Umami cache
// ─────────────────────────────────────────────────────────────────
blogSllRouter.post("/sll/sync-umami", async (_req: Request, res: Response) => {
  umamiCache = null; // invalidate
  const slots = await fetchUmamiSlots();
  res.json({
    success: slots.length > 0,
    slots_loaded: slots.length,
    total_sessions: slots.reduce((s, x) => s + x.sessions, 0),
    message: slots.length > 0 ? "Umami cache refreshed" : "Umami unreachable — check credentials",
  });
});

// ─────────────────────────────────────────────────────────────────
// GET /api/blog/sll/patterns — all learned patterns
// ─────────────────────────────────────────────────────────────────
blogSllRouter.get("/sll/patterns", async (_req: Request, res: Response) => {
  try {
    const result = await pool.query(`
      SELECT lp.*, d.title as example_title
      FROM blog_learned_patterns lp
      LEFT JOIN blog_drafts d ON d.id = lp.example_post_id
      WHERE lp.active = TRUE
      ORDER BY lp.performance_class, lp.avg_engagement DESC NULLS LAST
    `);

    res.json({ success: true, patterns: result.rows });
  } catch (err) {
    res.status(500).json({ success: false, error: String(err) });
  }
});

// ─────────────────────────────────────────────────────────────────
// POST /api/blog/sll/analyze — trigger LLM pattern extraction
// ─────────────────────────────────────────────────────────────────
blogSllRouter.post("/sll/analyze", async (_req: Request, res: Response) => {
  try {
    // Need at least 3 posts with performance data
    const countRes = await pool.query(
      `SELECT COUNT(*) as cnt FROM blog_performance WHERE engagement_score IS NOT NULL`
    );
    const count = Number(countRes.rows[0].cnt);
    if (count < 3) {
      res.status(400).json({
        success: false,
        error: `Not enough data — need at least 3 posts with performance data, have ${count}`,
      });
      return;
    }

    // Pull all performance data with article context
    const perfData = await pool.query(`
      SELECT
        d.title, d.topic, d.word_count as article_words, d.pipeline_version,
        p.engagement_score, p.hook_text, p.blog_type, p.comments, p.shares,
        p.saves, p.likes, p.impressions,
        CASE WHEN p.engagement_score >= 20 THEN 'gold'
             WHEN p.engagement_score >= 10 THEN 'silver'
             WHEN p.engagement_score >= 4  THEN 'bronze'
             ELSE 'miss' END as tier
      FROM blog_performance p
      JOIN blog_drafts d ON d.id = p.blog_id
      ORDER BY p.engagement_score DESC
    `);

    // Sort into winners / losers
    const winners = perfData.rows.filter((r: any) => r.engagement_score >= 10);
    const losers  = perfData.rows.filter((r: any) => r.engagement_score < 4);

    // Build LLM prompt for pattern extraction
    const analysisPrompt = `You are analyzing LinkedIn post performance data for the Flexoptix technical blog.

PERFORMANCE DATA (${count} posts):

TOP PERFORMERS:
${winners.map((r: any, i: number) => `${i+1}. Score: ${r.engagement_score} (${r.tier})
   Title: ${r.title}
   Topic: ${r.topic} | Words: ${r.article_words}
   Hook: "${r.hook_text || "n/a"}"
   Metrics: ${r.comments}c / ${r.shares}sh / ${r.saves}sa / ${r.likes}li`).join("\n\n") || "No winners yet"}

UNDERPERFORMERS:
${losers.map((r: any, i: number) => `${i+1}. Score: ${r.engagement_score}
   Title: ${r.title}
   Topic: ${r.topic} | Words: ${r.article_words}
   Hook: "${r.hook_text || "n/a"}"`).join("\n\n") || "No losers yet"}

SCORING FORMULA: (comments×3) + (shares×2) + (saves×2)
Likes = 0 weight. Saves and shares = real interest.

Extract patterns in this EXACT JSON format:
{
  "winner_patterns": [
    {"type": "hook|structure|topic|length|opening", "value": "description of what works", "evidence": "why you think this"}
  ],
  "loser_patterns": [
    {"type": "hook|structure|topic|length|opening", "value": "description of what fails", "evidence": "why"}
  ],
  "optimal_length": {"min": 900, "max": 1400},
  "top_topics": ["topic1", "topic2"],
  "avoid_topics": ["topic3"],
  "best_hook_patterns": ["pattern1", "pattern2"],
  "key_insight": "One sentence: the single most important finding from this data"
}

Be specific. "short hook + contrast" is better than "good hooks". Use the actual data.`;

    // Call LLM
    let extractedPatterns: Record<string, unknown> | null = null;
    try {
      const { generate } = await import("../llm/client");
      const llmResult = await generate(
        "You are a content analytics expert extracting patterns from performance data. Return only valid JSON.",
        analysisPrompt,
        { temperature: 0.2, maxTokens: 2048, timeoutMs: 120000 }
      );

      const jsonMatch = llmResult.text.match(/\{[\s\S]*"winner_patterns"[\s\S]*\}/);
      if (jsonMatch) {
        extractedPatterns = JSON.parse(jsonMatch[0]);
      }
    } catch (llmErr) {
      console.warn("SLL LLM extraction failed, using heuristic fallback:", llmErr);
    }

    // Heuristic fallback if LLM fails
    if (!extractedPatterns) {
      const avgWinner = winners.length > 0
        ? winners.reduce((s: number, r: any) => s + Number(r.article_words), 0) / winners.length
        : 1100;
      const avgLoser = losers.length > 0
        ? losers.reduce((s: number, r: any) => s + Number(r.article_words), 0) / losers.length
        : 1400;

      extractedPatterns = {
        winner_patterns: [
          { type: "length", value: `~${Math.round(avgWinner)} words performs best`, evidence: "heuristic from top posts" },
        ],
        loser_patterns: [
          { type: "length", value: `~${Math.round(avgLoser)} words underperforms`, evidence: "heuristic from low posts" },
        ],
        optimal_length: { min: Math.round(avgWinner * 0.8), max: Math.round(avgWinner * 1.2) },
        top_topics: [...new Set(winners.map((r: any) => r.topic))].slice(0, 3),
        avoid_topics: [...new Set(losers.map((r: any) => r.topic))].slice(0, 2),
        best_hook_patterns: winners.filter((r: any) => r.hook_text).map((r: any) => r.hook_text).slice(0, 2),
        key_insight: "Based on available data — more posts needed for reliable patterns",
      };
    }

    const ep = extractedPatterns as any;

    // Save patterns to DB (upsert by value to avoid duplicates)
    let savedCount = 0;
    const allPatterns = [
      ...(ep.winner_patterns || []).map((p: any) => ({ ...p, cls: "winner" })),
      ...(ep.loser_patterns || []).map((p: any) => ({ ...p, cls: "loser" })),
    ];

    for (const p of allPatterns) {
      await pool.query(
        `INSERT INTO blog_learned_patterns (pattern_type, pattern_value, performance_class, sample_count)
         VALUES ($1, $2, $3, $4)
         ON CONFLICT DO NOTHING`,
        [p.type, p.value, p.cls, count]
      );
      savedCount++;
    }

    // Save weekly SLL state
    const weekStart = new Date();
    weekStart.setDate(weekStart.getDate() - weekStart.getDay()); // Monday
    await pool.query(
      `INSERT INTO blog_sll_state
         (week_start, winner_patterns, loser_patterns, top_topics, avoid_topics,
          optimal_length_min, optimal_length_max, best_hook_patterns, posts_analyzed, generated_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'sll-analyze')
       ON CONFLICT (week_start) DO UPDATE SET
         winner_patterns = EXCLUDED.winner_patterns,
         loser_patterns = EXCLUDED.loser_patterns,
         top_topics = EXCLUDED.top_topics,
         avoid_topics = EXCLUDED.avoid_topics,
         optimal_length_min = EXCLUDED.optimal_length_min,
         optimal_length_max = EXCLUDED.optimal_length_max,
         best_hook_patterns = EXCLUDED.best_hook_patterns,
         posts_analyzed = EXCLUDED.posts_analyzed,
         generated_at = NOW()`,
      [
        weekStart.toISOString().split("T")[0],
        JSON.stringify(ep.winner_patterns || []),
        JSON.stringify(ep.loser_patterns || []),
        JSON.stringify(ep.top_topics || []),
        JSON.stringify(ep.avoid_topics || []),
        ep.optimal_length?.min ?? null,
        ep.optimal_length?.max ?? null,
        JSON.stringify(ep.best_hook_patterns || []),
        count,
      ]
    );

    console.log(`SLL: Pattern analysis complete — ${savedCount} patterns saved, ${count} posts analyzed`);

    res.json({
      success: true,
      posts_analyzed: count,
      patterns_saved: savedCount,
      key_insight: ep.key_insight,
      winner_patterns: ep.winner_patterns,
      loser_patterns: ep.loser_patterns,
      optimal_length: ep.optimal_length,
      top_topics: ep.top_topics,
      best_hook_patterns: ep.best_hook_patterns,
    });
  } catch (err) {
    console.error("SLL analyze error:", err);
    res.status(500).json({ success: false, error: String(err) });
  }
});
