import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

// ─── Simple YAML parser for the subset of YAML we expect ───

/** Strip surrounding quotes from a string */
function stripQuotes(s: string): string {
  if (
    (s.startsWith('"') && s.endsWith('"')) ||
    (s.startsWith("'") && s.endsWith("'"))
  ) {
    return s.slice(1, -1);
  }
  return s;
}

/** Parse a scalar value (string, number, boolean, null) */
function parseScalar(raw: string): string | number | boolean | null {
  const trimmed = raw.trim();
  if (trimmed === "null" || trimmed === "~") return null;
  if (trimmed === "true") return true;
  if (trimmed === "false") return false;
  // Try number
  const num = Number(trimmed);
  if (!isNaN(num) && trimmed !== "") return num;
  return stripQuotes(trimmed);
}

interface ParseResult {
  value: any;
  nextLine: number;
}

/**
 * Parse a block of YAML lines starting at `startLine`.
 * Detects whether the block is a list (lines start with `- ` at base indent)
 * or an object (key: value pairs).
 */
function parseBlock(lines: string[], startLine: number, baseIndent: number): ParseResult {
  // Skip empty lines
  let i = startLine;
  while (i < lines.length && lines[i].trim() === "") i++;

  if (i >= lines.length) return { value: null, nextLine: i };

  const firstNonEmpty = lines[i];

  // Detect leading whitespace of the first content line
  const firstIndent = firstNonEmpty.search(/\S/);

  // Check if this is a list (first non-empty line starts with "- " at base indent)
  if (firstNonEmpty.trimStart().startsWith("- ")) {
    return parseList(lines, i, firstIndent);
  }

  // Otherwise treat as an object (key: value)
  return parseMapping(lines, i, firstIndent);
}

/** Parse a YAML sequence (list) */
function parseList(lines: string[], startLine: number, indent: number): ParseResult {
  const items: any[] = [];
  let i = startLine;

  while (i < lines.length) {
    const trimmed = lines[i].trim();
    if (trimmed === "") {
      i++;
      continue;
    }
    // Check current indent
    const currentIndent = lines[i].search(/\S/);
    if (currentIndent < indent) break; // outdented, we're done
    if (currentIndent !== indent) {
      // If it's indented more, it might be part of the previous item's value
      // For our use case, this shouldn't happen at the top level of a list
      i++;
      continue;
    }

    if (!trimmed.startsWith("- ")) {
      // Not a list item anymore
      break;
    }

    const rest = trimmed.slice(2).trim();

    // Check if the item has inline value or is a sub-block
    if (rest === "") {
      // Empty list item - could have sub-items indented below
      // Peek ahead for sub-block
      if (i + 1 < lines.length) {
        const nextIndent = lines[i + 1].search(/\S/);
        if (nextIndent > indent) {
          const sub = parseBlock(lines, i + 1, nextIndent);
          items.push(sub.value);
          i = sub.nextLine;
          continue;
        }
      }
      items.push(null);
      i++;
    } else if (rest.includes(":") && !rest.startsWith('"') && !rest.startsWith("'")) {
      // The item might be an inline mapping start: "key: value" or just "key:"
      // Actually, for our format, modules have: `- slug: "value"` which is inline
      // Let's check if there are sub-properties on subsequent lines
      if (i + 1 < lines.length) {
        const nextIndent = lines[i + 1].search(/\S/);
        if (nextIndent > indent) {
          // This list item has sub-properties; parse the rest inline as a mapping
          // Treat this line as the first key:value of the sub-mapping
          const subResult = parseMappingFromLine(lines, i, indent);
          items.push(subResult.value);
          i = subResult.nextLine;
          continue;
        }
      }
      // No sub-properties, treat the whole line as a mapping key:value
      const colonIdx = rest.indexOf(":");
      const key = stripQuotes(rest.slice(0, colonIdx).trim());
      const valStr = rest.slice(colonIdx + 1).trim();
      const val = valStr ? parseScalar(valStr) : null;
      const obj: Record<string, any> = {};
      obj[key] = val;
      items.push(obj);
      i++;
    } else {
      // Simple scalar list item
      items.push(parseScalar(rest));
      i++;
    }
  }

  return { value: items, nextLine: i };
}

/**
 * Parse a mapping (key: value) starting from a specific line.
 * The mapping ends when indentation returns to or above baseIndent.
 */
function parseMapping(lines: string[], startLine: number, baseIndent: number): ParseResult {
  const result: Record<string, any> = {};
  let i = startLine;

  while (i < lines.length) {
    const line = lines[i];
    const trimmedLine = line.trim();
    if (trimmedLine === "") {
      i++;
      continue;
    }

    const currentIndent = line.search(/\S/);
    if (currentIndent < baseIndent) break; // outdented
    if (currentIndent !== baseIndent) {
      // This shouldn't happen at the mapping level, skip
      i++;
      continue;
    }

    // Must be a key: value pair
    if (!trimmedLine.includes(":")) {
      i++;
      continue;
    }

    const colonIdx = trimmedLine.indexOf(":");
    const key = stripQuotes(trimmedLine.slice(0, colonIdx).trim());
    let rest = trimmedLine.slice(colonIdx + 1).trim();

    if (rest === "") {
      // Value might be a nested block on subsequent lines
      // Check next line
      if (i + 1 < lines.length) {
        const nextIndent = lines[i + 1].search(/\S/);
        if (nextIndent > currentIndent) {
          const subResult = parseBlock(lines, i + 1, nextIndent);
          result[key] = subResult.value;
          i = subResult.nextLine;
          continue;
        }
      }
      // No value or empty value
      result[key] = null;
      i++;
    } else {
      // Inline scalar value
      result[key] = parseScalar(rest);
      i++;
    }
  }

  return { value: result, nextLine: i };
}

/**
 * Special case: when a list item like `- slug: "value"` has sub-properties
 * on subsequent indented lines, parse it as a mapping starting with the current
 * line's content.
 */
function parseMappingFromLine(lines: string[], startLine: number, parentIndent: number): ParseResult {
  const result: Record<string, any> = {};
  let i = startLine;

  // Parse the first line which already has content like "- slug: value"
  const firstLine = lines[i];
  const afterDash = firstLine.trim().slice(2).trim();
  const colonIdx = afterDash.indexOf(":");
  const firstKey = stripQuotes(afterDash.slice(0, colonIdx).trim());
  const firstValStr = afterDash.slice(colonIdx + 1).trim();
  result[firstKey] = firstValStr ? parseScalar(firstValStr) : null;
  i++;

  // Now parse subsequent lines at a higher indent
  const subIndent = parentIndent + 2; // typical YAML indent
  while (i < lines.length) {
    const line = lines[i];
    const trimmedLine = line.trim();
    if (trimmedLine === "") {
      i++;
      continue;
    }

    const currentIndent = line.search(/\S/);
    if (currentIndent <= parentIndent) break; // back to parent level

    if (!trimmedLine.includes(":")) {
      i++;
      continue;
    }

    const ci = trimmedLine.indexOf(":");
    const key = stripQuotes(trimmedLine.slice(0, ci).trim());
    const rest = trimmedLine.slice(ci + 1).trim();

    if (rest === "") {
      // Could have nested value below
      if (i + 1 < lines.length) {
        const nextIndent = lines[i + 1].search(/\S/);
        if (nextIndent > currentIndent) {
          const subResult = parseBlock(lines, i + 1, nextIndent);
          result[key] = subResult.value;
          i = subResult.nextLine;
          continue;
        }
      }
      result[key] = null;
      i++;
    } else {
      result[key] = parseScalar(rest);
      i++;
    }
  }

  return { value: result, nextLine: i };
}

// ─── Gitea API helpers ───

const GITEA_BASE = "https://git.falahos.my/api/v1/repos/maifors/courses/contents";

function getGiteaToken(): string {
  const token = process.env.GITEA_TOKEN;
  if (!token) {
    throw new Error("GITEA_TOKEN environment variable is not set");
  }
  return token;
}

interface GiteaFileResponse {
  name: string;
  path: string;
  content: string;
  encoding: string;
}

async function fetchGiteaFile(filePath: string): Promise<string> {
  const url = `${GITEA_BASE}/${filePath}`;
  const token = getGiteaToken();

  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
      Accept: "application/json",
    },
  });

  if (!res.ok) {
    throw new Error(
      `Gitea API error: ${res.status} ${res.statusText} for ${filePath}`
    );
  }

  const data: GiteaFileResponse = await res.json();
  if (data.encoding !== "base64" || !data.content) {
    throw new Error(`Unexpected Gitea response format for ${filePath}`);
  }

  return Buffer.from(data.content, "base64").toString("utf-8");
}

// ─── Course sync logic ───

interface CourseYaml {
  title?: string;
  author?: string;
  description?: string;
  imageUrl?: string;
  difficulty?: string;
  priceFlh?: number;
  priceUsd?: number;
  subscriptionOnly?: boolean;
  published?: boolean;
  modules?: Array<{
    slug?: string;
    title?: string;
    keyTakeaway?: string;
    duration?: number;
  }>;
}

interface QuizQuestion {
  question?: string;
  options?: string[];
  correctIndex?: number;
}

interface QuizYaml {
  questions?: QuizQuestion[];
}

async function syncCourse(slug: string): Promise<void> {
  // Fetch course.yaml
  const courseYamlText = await fetchGiteaFile(`courses/${slug}/course.yaml`);
  const parsed = parseBlock(courseYamlText.split("\n"), 0, 0);
  const courseData = parsed.value as CourseYaml;

  if (!courseData || typeof courseData !== "object") {
    throw new Error(`Invalid course.yaml for slug: ${slug}`);
  }

  const giteaPath = `courses/${slug}`;

  // Upsert the course
  const course = await prisma.learnCourse.upsert({
    where: { slug },
    create: {
      slug,
      title: courseData.title || slug,
      author: courseData.author || "Unknown",
      description: courseData.description || "",
      imageUrl: courseData.imageUrl || null,
      difficulty: courseData.difficulty || "beginner",
      giteaPath,
      priceFlh: courseData.priceFlh ?? null,
      priceUsd: courseData.priceUsd ?? null,
      subscriptionOnly: courseData.subscriptionOnly ?? false,
      published: courseData.published ?? false,
      moduleCount: 0,
      totalMinutes: 0,
    },
    update: {
      title: courseData.title || slug,
      author: courseData.author || "Unknown",
      description: courseData.description || "",
      imageUrl: courseData.imageUrl || null,
      difficulty: courseData.difficulty || "beginner",
      giteaPath,
      priceFlh: courseData.priceFlh ?? null,
      priceUsd: courseData.priceUsd ?? null,
      subscriptionOnly: courseData.subscriptionOnly ?? false,
      published: courseData.published ?? false,
    },
  });

  // Sync modules
  let totalMinutes = 0;
  const modules = courseData.modules || [];

  for (let order = 0; order < modules.length; order++) {
    const modData = modules[order];
    if (!modData || !modData.slug) continue;

    const modSlug = modData.slug;
    const modTitle = modData.title || modSlug;

    // Fetch lesson.md
    let lessonContent: string | null = null;
    try {
      lessonContent = await fetchGiteaFile(
        `courses/${slug}/modules/${modSlug}/lesson.md`
      );
    } catch {
      // lesson.md might not exist yet
      lessonContent = null;
    }

    // Fetch quiz.yaml
    let quizData: string | null = null;
    try {
      const quizYamlText = await fetchGiteaFile(
        `courses/${slug}/modules/${modSlug}/quiz.yaml`
      );
      const quizParsed = parseBlock(quizYamlText.split("\n"), 0, 0);
      const quiz = quizParsed.value as QuizYaml;
      if (quiz && Array.isArray(quiz.questions)) {
        quizData = JSON.stringify(quiz.questions);
      }
    } catch {
      // quiz.yaml might not exist yet
      quizData = null;
    }

    const duration = modData.duration || 5;
    totalMinutes += duration;

    await prisma.learnModule.upsert({
      where: {
        courseId_slug: {
          courseId: course.id,
          slug: modSlug,
        },
      },
      create: {
        courseId: course.id,
        order,
        title: modTitle,
        slug: modSlug,
        keyTakeaway: modData.keyTakeaway || null,
        duration,
        content: lessonContent,
        quizData,
      },
      update: {
        order,
        title: modTitle,
        keyTakeaway: modData.keyTakeaway || null,
        duration,
        content: lessonContent ?? undefined,
        quizData: quizData ?? undefined,
      },
    });
  }

  // Update course module count and total minutes
  await prisma.learnCourse.update({
    where: { id: course.id },
    data: {
      moduleCount: modules.length,
      totalMinutes,
    },
  });
}

// ─── Route handlers ───

/**
 * GET /api/learn/sync — health check
 */
export async function GET() {
  return NextResponse.json({ status: "ok", lastSync: null });
}

/**
 * POST /api/learn/sync — Gitea webhook receiver
 *
 * Accepts a secret token via ?token= query param or Authorization: Bearer <token> header.
 * Compares against SYNC_SECRET env var.
 * On success, fetches courses from Gitea and syncs to local DB.
 */
export async function POST(req: NextRequest) {
  try {
    // 1. Validate secret token
    const url = new URL(req.url);
    const token =
      url.searchParams.get("token") ||
      req.headers.get("Authorization")?.replace(/^Bearer\s+/i, "");

    if (!token || token !== process.env.SYNC_SECRET) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    // 2. Fetch course index from Gitea
    const indexYamlText = await fetchGiteaFile("courses/course-index.yaml");
    const indexParsed = parseBlock(indexYamlText.split("\n"), 0, 0);
    const indexValue = indexParsed.value;

    // The index should be a simple list of slugs
    let courseSlugs: string[] = [];
    if (Array.isArray(indexValue)) {
      courseSlugs = indexValue
        .map((item: any) => (typeof item === "string" ? item.trim() : null))
        .filter((s: string | null): s is string => !!s);
    } else if (typeof indexValue === "object" && indexValue !== null) {
      // Fallback: object keys as slugs
      courseSlugs = Object.keys(indexValue);
    }

    if (courseSlugs.length === 0) {
      return NextResponse.json(
        { error: "No courses found in course-index.yaml" },
        { status: 400 }
      );
    }

    // 3. Sync each course
    const syncedCourses: string[] = [];

    for (const slug of courseSlugs) {
      try {
        await syncCourse(slug);
        syncedCourses.push(slug);
      } catch (err) {
        console.error(`Failed to sync course "${slug}":`, err);
        // Continue with other courses
      }
    }

    return NextResponse.json({
      synced: syncedCourses.length,
      courses: syncedCourses,
    });
  } catch (error) {
    console.error("Gitea sync error:", error);
    return NextResponse.json(
      { error: "Sync failed: " + (error instanceof Error ? error.message : "Unknown error") },
      { status: 500 }
    );
  }
}
