import { NextResponse } from "next/server";
import { sql } from "@/db";
import { requireFamily } from "@/lib/auth";
import { S3Client, PutObjectCommand, CopyObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const R2_PUBLIC_URL = process.env.R2_PUBLIC_URL ?? "";

function makeR2Client() {
  return new S3Client({
    region: "auto",
    endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
    credentials: {
      accessKeyId:     process.env.R2_ACCESS_KEY_ID!,
      secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
    },
  });
}

async function assertMember(circleId: string, familyId: string) {
  const rows = await sql.unsafe(
    `SELECT role FROM circle_members WHERE circle_id = $1 AND family_id = $2`,
    [circleId, familyId]
  );
  return rows[0]?.role ?? null;
}

// GET — circle feed (newest first)
export async function GET(
  req: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const auth = await requireFamily();
  if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });

  const familyId = auth.session!.familyId!;
  const { id: circleId } = await params;

  if (!(await assertMember(circleId, familyId))) {
    return NextResponse.json({ error: "Not a member" }, { status: 403 });
  }

  const { searchParams } = new URL(req.url);
  const limit  = Math.min(parseInt(searchParams.get("limit")  ?? "20"), 50);
  const before = searchParams.get("before"); // cursor: ISO timestamp

  const posts = await sql.unsafe(
    `SELECT p.id, p.circle_id as "circleId", p.author_family_id as "authorFamilyId",
            f.name as "authorFamilyName", p.body, p.image_key as "imageKey",
            p.source_kind as "sourceKind", p.created_at as "createdAt",
            (SELECT COUNT(*) FROM circle_post_comments WHERE post_id = p.id)::int as "commentCount"
     FROM circle_posts p
     JOIN families f ON f.id = p.author_family_id
     WHERE p.circle_id = $1
       AND ($2::timestamptz IS NULL OR p.created_at < $2::timestamptz)
     ORDER BY p.created_at DESC
     LIMIT $3`,
    [circleId, before ?? null, limit]
  );

  // Attach reactions per post
  const postIds = posts.map((p: Record<string, unknown>) => p.id as string);
  let reactions: Array<{ postId: string; emoji: string; count: number; familyId: string }> = [];
  if (postIds.length > 0) {
    reactions = await sql.unsafe(
      `SELECT post_id as "postId", emoji,
              COUNT(*)::int as count,
              bool_or(family_id = $2) as "includedMe"
       FROM circle_post_reactions
       WHERE post_id = ANY($1::uuid[])
       GROUP BY post_id, emoji`,
      [postIds, familyId]
    );
  }

  const reactionsByPost = reactions.reduce<Record<string, typeof reactions>>((acc, r) => {
    if (!acc[r.postId]) acc[r.postId] = [];
    acc[r.postId].push(r);
    return acc;
  }, {});

  type PostRow = Record<string, unknown> & { id: string; imageKey: string | null };
  const result = (posts as unknown as PostRow[]).map(p => ({
    ...p,
    imageUrl:  p.imageKey ? `${R2_PUBLIC_URL}/${p.imageKey}` : null,
    reactions: (reactionsByPost[p.id] ?? []).map(r => ({
      emoji: r.emoji, count: r.count, includedMe: r.familyId === familyId,
    })),
  }));

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

// POST — create a post or request a presigned upload URL
export async function POST(
  req: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const auth = await requireFamily();
  if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });

  const familyId = auth.session!.familyId!;
  const { id: circleId } = await params;

  if (!(await assertMember(circleId, familyId))) {
    return NextResponse.json({ error: "Not a member" }, { status: 403 });
  }

  const body = await req.json();

  // ── Sub-action: get presigned upload URL for a circle post image ──────────
  if (body.action === "presign") {
    try {
      const { filename } = body;
      let { contentType } = body as { contentType?: string };

      // Normalise empty/missing content type by sniffing the extension
      if (!contentType) {
        const ext = (filename ?? "").split(".").pop()?.toLowerCase() ?? "";
        const EXT_MAP: Record<string, string> = {
          jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png",
          webp: "image/webp", heic: "image/heic", heif: "image/heif", gif: "image/gif",
        };
        contentType = EXT_MAP[ext] ?? "";
      }

      const ALLOWED = ["image/jpeg", "image/jpg", "image/png", "image/webp",
                       "image/heic", "image/heif", "image/gif"];
      if (!ALLOWED.includes(contentType)) {
        return NextResponse.json({ error: `Unsupported file type: ${contentType || "unknown"}` }, { status: 400 });
      }

      const ext    = filename?.split(".").pop()?.toLowerCase() ?? "jpg";
      const tmpKey = `circle-posts/tmp/${familyId}/${Date.now()}.${ext}`;
      const uploadUrl = await getSignedUrl(
        makeR2Client(),
        new PutObjectCommand({ Bucket: process.env.R2_BUCKET_NAME!, Key: tmpKey, ContentType: contentType }),
        { expiresIn: 3600 }
      );
      return NextResponse.json({ uploadUrl, tmpKey, publicUrl: `${R2_PUBLIC_URL}/${tmpKey}`, contentType });
    } catch (err: unknown) {
      return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 });
    }
  }

  // ── Sub-action: share from a private memory (copy-on-share) ──────────────
  if (body.action === "shareMemory") {
    const { memoryId, postBody } = body;
    // Verify the family owns this memory
    const [memory] = await sql.unsafe(
      `SELECT r2_key, r2_thumbnail_key FROM memories WHERE id = $1 AND family_id = $2`,
      [memoryId, familyId]
    );
    if (!memory) return NextResponse.json({ error: "Memory not found or not yours" }, { status: 404 });

    // Create post row first to get the post ID
    const [post] = await sql.unsafe(
      `INSERT INTO circle_posts (circle_id, author_family_id, body, source_kind)
       VALUES ($1, $2, $3, 'memory')
       RETURNING id`,
      [circleId, familyId, postBody ?? null]
    );

    // Copy image to circle-posts/{post_id}/ — independent object, never references original
    let copiedKey: string | null = null;
    if (memory.r2_key) {
      const ext       = memory.r2_key.split(".").pop() ?? "jpg";
      copiedKey       = `circle-posts/${post.id}/image.${ext}`;
      await makeR2Client().send(new CopyObjectCommand({
        Bucket:     process.env.R2_BUCKET_NAME!,
        CopySource: `${process.env.R2_BUCKET_NAME}/${memory.r2_key}`,
        Key:        copiedKey,
      }));
      await sql.unsafe(
        `UPDATE circle_posts SET image_key = $1 WHERE id = $2`,
        [copiedKey, post.id]
      );
    }

    return NextResponse.json({ success: true, postId: post.id });
  }

  // ── Default: create a fresh post ─────────────────────────────────────────
  const { postBody, tmpKey } = body;
  if (!postBody?.trim() && !tmpKey) {
    return NextResponse.json({ error: "Post must have text or an image" }, { status: 400 });
  }

  const [post] = await sql.unsafe(
    `INSERT INTO circle_posts (circle_id, author_family_id, body)
     VALUES ($1, $2, $3)
     RETURNING id`,
    [circleId, familyId, postBody?.trim() ?? null]
  );

  // Move temp image to permanent key under circle-posts/{post_id}/
  let finalKey: string | null = null;
  if (tmpKey) {
    const ext    = tmpKey.split(".").pop() ?? "jpg";
    finalKey     = `circle-posts/${post.id}/image.${ext}`;
    await makeR2Client().send(new CopyObjectCommand({
      Bucket:     process.env.R2_BUCKET_NAME!,
      CopySource: `${process.env.R2_BUCKET_NAME}/${tmpKey}`,
      Key:        finalKey,
    }));
    await sql.unsafe(
      `UPDATE circle_posts SET image_key = $1 WHERE id = $2`,
      [finalKey, post.id]
    );
  }

  return NextResponse.json({ success: true, postId: post.id });
}
