import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireMembership } from "@/lib/blindbox";
import { apiHandler, ApiError, requireUserId } from "@/lib/api";
import { requireAmapApiKey } from "@/lib/amap";
import { generateSchedule, type ScheduleContext } from "@/lib/ai";

interface AvailableTime {
  date: string;
  startHour: number;
  endHour: number;
}

interface TaggedIdea {
  id: string;
  content: string;
  category: string;
  timeSlot: string;
  estimatedMinutes: number;
  searchQuery: string;
  searchType: string;
}

const SLOT_CATEGORY_MAP: Record<string, string[]> = {
  morning: ["outdoor", "sports", "culture"],
  lunch: ["dining"],
  afternoon: ["entertainment", "shopping", "relaxation", "outdoor", "culture"],
  dinner: ["dining"],
  evening: ["entertainment", "relaxation"],
};

function selectIdeasForSlots(ideas: TaggedIdea[], availableHours: number): TaggedIdea[] {
  const byCategory = new Map<string, TaggedIdea[]>();
  for (const idea of ideas) {
    const list = byCategory.get(idea.category) || [];
    list.push(idea);
    byCategory.set(idea.category, list);
  }

  const slots: string[] = [];
  if (availableHours >= 10) {
    slots.push("morning", "lunch", "afternoon", "dinner", "evening");
  } else if (availableHours >= 7) {
    slots.push("morning", "lunch", "afternoon", "evening");
  } else if (availableHours >= 5) {
    slots.push("lunch", "afternoon", "evening");
  } else {
    slots.push("afternoon", "evening");
  }

  const selected: TaggedIdea[] = [];
  const usedIds = new Set<string>();

  for (const slot of slots) {
    const preferredCategories = SLOT_CATEGORY_MAP[slot] || [];

    let picked: TaggedIdea | null = null;
    for (const cat of preferredCategories) {
      const pool = (byCategory.get(cat) || []).filter((i) => !usedIds.has(i.id));
      if (pool.length > 0) {
        picked = pool[Math.floor(Math.random() * pool.length)];
        break;
      }
    }

    if (!picked) {
      const remaining = ideas.filter((i) => !usedIds.has(i.id));
      if (remaining.length > 0) {
        picked = remaining[Math.floor(Math.random() * remaining.length)];
      }
    }

    if (picked) {
      selected.push(picked);
      usedIds.add(picked.id);
    }
  }

  return selected;
}

async function searchPois(
  query: string,
  searchType: string,
  anchorLat: number,
  anchorLng: number,
): Promise<{ name: string; address: string; lat: number; lng: number; rating?: number }[]> {
  const apiKey = requireAmapApiKey();

  if (searchType === "category") {
    const url = new URL("https://restapi.amap.com/v5/place/around");
    url.searchParams.set("key", apiKey);
    url.searchParams.set("location", `${anchorLng},${anchorLat}`);
    url.searchParams.set("keywords", query);
    url.searchParams.set("radius", "5000");
    url.searchParams.set("show_fields", "business");
    url.searchParams.set("page_size", "8");

    const res = await fetch(url.toString());
    const data = await res.json();
    if (data.status !== "1" || !data.pois?.length) return [];
    return mapPois(data.pois);
  }

  // Text/brand search — bias results to the room's location
  const url = new URL("https://restapi.amap.com/v5/place/text");
  url.searchParams.set("key", apiKey);
  url.searchParams.set("keywords", query);
  url.searchParams.set("location", `${anchorLng},${anchorLat}`);
  url.searchParams.set("show_fields", "business");
  url.searchParams.set("page_size", "8");

  const res = await fetch(url.toString());
  const data = await res.json();
  if (data.status !== "1" || !data.pois?.length) return [];
  return mapPois(data.pois);
}

function mapPois(
  pois: { name: string; address?: string; location?: string; business?: { rating?: string } }[],
) {
  return pois
    .filter((p) => p.location)
    .map((p) => {
      const [lng, lat] = (p.location ?? "0,0").split(",").map(Number);
      const ratingStr = p.business?.rating;
      return {
        name: p.name,
        address: p.address || "",
        lat,
        lng,
        rating: ratingStr && ratingStr !== "[]" ? parseFloat(ratingStr) || undefined : undefined,
      };
    });
}

export const POST = apiHandler(async (req) => {
  const { roomId, userId, availableTime } = await req.json();

  requireUserId(userId);
  if (!roomId) throw new ApiError("roomId 不能为空");

  await requireMembership(roomId, userId);

  const at = availableTime as AvailableTime;
  if (
    !at?.date ||
    typeof at.startHour !== "number" ||
    typeof at.endHour !== "number" ||
    at.endHour <= at.startHour
  ) {
    throw new ApiError("请选择有效的可用时间");
  }

  const room = await prisma.blindBoxRoom.findUnique({ where: { id: roomId } });
  if (!room) throw new ApiError("房间不存在", 404);
  if (!room.lat || !room.lng) {
    throw new ApiError("请先设置房间位置", 400);
  }

  const allIdeas = await prisma.blindBoxIdea.findMany({
    where: { roomId, status: "in_pool", category: { not: null } },
    select: {
      id: true,
      content: true,
      category: true,
      timeSlot: true,
      estimatedMinutes: true,
      searchQuery: true,
      searchType: true,
    },
  });

  const taggedIdeas: TaggedIdea[] = allIdeas.filter(
    (i): i is TaggedIdea =>
      !!i.category && !!i.timeSlot && !!i.searchQuery && !!i.searchType &&
      typeof i.estimatedMinutes === "number",
  );

  if (taggedIdeas.length < 2) {
    throw new ApiError("盒子里至少需要 2 个已标记的想法才能生成计划", 400);
  }

  // Split into day configs — "整个周末" generates two separate days
  const dayConfigs: AvailableTime[] =
    at.date === "整个周末"
      ? [
          { date: "周六", startHour: at.startHour, endHour: at.endHour },
          { date: "周日", startHour: at.startHour, endHour: at.endHour },
        ]
      : [at];

  // Select ideas per day — skip extra days when ideas run out
  const dayIdeas: TaggedIdea[][] = [];
  const usedIds = new Set<string>();
  for (const dayConfig of dayConfigs) {
    const remaining = taggedIdeas.filter((i) => !usedIds.has(i.id));
    if (remaining.length < 2) break;
    const selected = selectIdeasForSlots(remaining, dayConfig.endHour - dayConfig.startHour);
    for (const idea of selected) usedIds.add(idea.id);
    dayIdeas.push(selected);
  }
  // Trim to actual days generated (may be fewer than requested for "整个周末")
  const actualDayConfigs = dayConfigs.slice(0, dayIdeas.length);

  const allSelected = dayIdeas.flat();
  if (allSelected.length === 0) {
    throw new ApiError("无法从想法池中选出合适的活动", 400);
  }

  // Deduplicate search queries across all days
  const uniqueByQuery = new Map<string, TaggedIdea>();
  for (const idea of allSelected) {
    if (!uniqueByQuery.has(idea.searchQuery)) uniqueByQuery.set(idea.searchQuery, idea);
  }

  // Phase 1: search brand/place type queries in parallel
  const brandPlaceQueries = [...uniqueByQuery.values()].filter((i) => i.searchType !== "category");

  const searchResults = await Promise.all(
    brandPlaceQueries.map(async (idea) => {
      try {
        const pois = await searchPois(idea.searchQuery, idea.searchType, room.lat!, room.lng!);
        return { query: idea.searchQuery, pois };
      } catch {
        return { query: idea.searchQuery, pois: [] };
      }
    }),
  );

  const candidates: ScheduleContext["candidates"] = {};
  for (const result of searchResults) {
    candidates[result.query] = result.pois;
  }

  // Phase 2: category-type queries anchored to centroid of found POIs
  const catQueries = [...uniqueByQuery.values()].filter((i) => i.searchType === "category");
  if (catQueries.length > 0) {
    const allPois = Object.values(candidates).flat();
    let anchorLat = room.lat;
    let anchorLng = room.lng;
    if (allPois.length > 0) {
      anchorLat = allPois.reduce((s, p) => s + p.lat, 0) / allPois.length;
      anchorLng = allPois.reduce((s, p) => s + p.lng, 0) / allPois.length;
    }

    const catResults = await Promise.all(
      catQueries.map(async (idea) => {
        try {
          const pois = await searchPois(idea.searchQuery, idea.searchType, anchorLat, anchorLng);
          return { query: idea.searchQuery, pois };
        } catch {
          return { query: idea.searchQuery, pois: [] };
        }
      }),
    );

    for (const result of catResults) {
      candidates[result.query] = result.pois;
    }
  }

  // Generate schedule for each day (parallel AI calls)
  const schedules = await Promise.all(
    actualDayConfigs.map((dayConfig, idx) => {
      const ideas = dayIdeas[idx];
      const ctx: ScheduleContext = {
        ideas: ideas.map((i) => ({
          content: i.content,
          category: i.category,
          timeSlot: i.timeSlot,
          estimatedMinutes: i.estimatedMinutes,
          searchQuery: i.searchQuery,
          searchType: i.searchType,
        })),
        candidates,
        userLocation: { lat: room.lat!, lng: room.lng! },
        availableTime: dayConfig,
      };
      return generateSchedule(ctx);
    }),
  );

  const days = schedules
    .map((schedule, idx) =>
      schedule
        ? { date: actualDayConfigs[idx].date, items: schedule.items, summary: schedule.summary }
        : null,
    )
    .filter((d) => d !== null);

  if (days.length === 0) {
    throw new ApiError("AI 规划失败，请稍后重试", 500);
  }

  const plan = await prisma.weekendPlan.create({
    data: {
      roomId,
      userId,
      planData: JSON.stringify({
        days,
        selectedIdeaIds: allSelected.map((i) => i.id),
      }),
    },
  });

  return NextResponse.json({
    id: plan.id,
    days,
    createdAt: plan.createdAt,
  });
});

/**
 * Map "周六"/"周日" to the next occurrence of that weekday from a reference date.
 * Returns a Date at 00:00 of that day.
 */
function nextWeekday(dayLabel: string, from: Date): Date {
  const targetDow = dayLabel === "周日" ? 0 : 6; // Sunday=0, Saturday=6
  const d = new Date(from);
  d.setHours(0, 0, 0, 0);
  const diff = (targetDow - d.getDay() + 7) % 7;
  d.setDate(d.getDate() + (diff === 0 ? 0 : diff));
  return d;
}

function computeEndTime(planData: string, now: Date): Date | null {
  try {
    const parsed = JSON.parse(planData);
    const days = parsed.days as { date: string; items: { time: string; duration: number }[] }[];
    if (!days?.length) return null;

    const lastDay = days[days.length - 1];
    const lastItem = lastDay.items[lastDay.items.length - 1];
    if (!lastItem) return null;

    const base = nextWeekday(lastDay.date, now);
    const [h, m] = lastItem.time.split(":").map(Number);
    base.setHours(h, m, 0, 0);
    base.setMinutes(base.getMinutes() + (lastItem.duration || 60));

    // If computed end time is in the past, it's for next week
    if (base.getTime() < now.getTime()) {
      base.setDate(base.getDate() + 7);
    }

    return base;
  } catch {
    return null;
  }
}

export const PATCH = apiHandler(async (req) => {
  const { planId, userId, action } = await req.json();
  requireUserId(userId);
  if (!planId) throw new ApiError("planId 不能为空");

  const plan = await prisma.weekendPlan.findUnique({ where: { id: planId } });
  if (!plan) throw new ApiError("计划不存在", 404);
  if (plan.userId !== userId) throw new ApiError("只能操作自己的计划", 403);

  const act = action || "accept";

  if (act === "accept") {
    if (plan.status !== "active") throw new ApiError("该计划无法接受", 400);
    const endTime = computeEndTime(plan.planData, new Date());
    await prisma.weekendPlan.update({
      where: { id: planId },
      data: { status: "accepted", endTime },
    });
    return NextResponse.json({ ok: true, endTime });
  }

  if (act === "complete" || act === "expire") {
    if (plan.status !== "accepted") throw new ApiError("只能更新已接受的计划", 400);
    await prisma.weekendPlan.update({
      where: { id: planId },
      data: { status: act === "complete" ? "completed" : "expired" },
    });
    return NextResponse.json({ ok: true });
  }

  throw new ApiError("无效的操作", 400);
});

export const GET = apiHandler(async (req) => {
  const { searchParams } = new URL(req.url);
  const mode = searchParams.get("mode") || "latest";
  const userId = searchParams.get("userId");
  requireUserId(userId);

  if (mode === "latest") {
    const roomId = searchParams.get("roomId");
    if (!roomId) throw new ApiError("roomId 不能为空");

    const plan = await prisma.weekendPlan.findFirst({
      where: { roomId, userId: userId!, status: "accepted" },
      orderBy: { createdAt: "desc" },
      select: { id: true, planData: true, endTime: true, createdAt: true },
    });

    if (!plan) return NextResponse.json({ plan: null });
    const parsed = JSON.parse(plan.planData);
    return NextResponse.json({
      plan: { id: plan.id, days: parsed.days, endTime: plan.endTime, createdAt: plan.createdAt },
    });
  }

  if (mode === "pending") {
    const plans = await prisma.weekendPlan.findMany({
      where: {
        userId: userId!,
        status: "accepted",
        endTime: { not: null, lt: new Date() },
      },
      orderBy: { createdAt: "desc" },
      select: { id: true, planData: true, roomId: true, createdAt: true },
      take: 5,
    });

    const result = await Promise.all(
      plans.map(async (p) => {
        const room = await prisma.blindBoxRoom.findUnique({
          where: { id: p.roomId },
          select: { name: true, code: true },
        });
        const parsed = JSON.parse(p.planData);
        const days = parsed.days as { date: string; items: { activity: string }[] }[];
        return {
          id: p.id,
          roomName: room?.name ?? "未知房间",
          roomCode: room?.code ?? "",
          date: days.map((d) => d.date).join(" + "),
          activities: days.flatMap((d) => d.items.map((i) => i.activity)),
          createdAt: p.createdAt,
        };
      }),
    );

    return NextResponse.json({ pending: result });
  }

  if (mode === "history") {
    const plans = await prisma.weekendPlan.findMany({
      where: {
        userId: userId!,
        status: { in: ["completed", "expired"] },
      },
      orderBy: { createdAt: "desc" },
      select: { id: true, planData: true, status: true, roomId: true, createdAt: true },
      take: 50,
    });

    const result = await Promise.all(
      plans.map(async (p) => {
        const room = await prisma.blindBoxRoom.findUnique({
          where: { id: p.roomId },
          select: { name: true, code: true },
        });
        const parsed = JSON.parse(p.planData);
        const days = parsed.days as { date: string; items: { activity: string }[] }[];
        return {
          id: p.id,
          status: p.status,
          roomName: room?.name ?? "未知房间",
          roomCode: room?.code ?? "",
          date: days.map((d) => d.date).join(" + "),
          dayCount: days.length,
          activities: days.flatMap((d) => d.items.map((i) => i.activity)),
          createdAt: p.createdAt,
        };
      }),
    );

    return NextResponse.json({ history: result });
  }

  throw new ApiError("无效的 mode 参数", 400);
});
