import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { NextRequest, NextResponse } from "next/server";
import { requireFamily, requireOwnership } from "@/lib/auth";
import { sql } from "@/db";
import { nanoid } from "nanoid";
import { checkStorageQuota } from "@/lib/quota";

const ALLOWED_TYPES = ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/heic", "image/gif"];
const MAX_BYTES = 20 * 1024 * 1024; // 20MB

function getR2Config() {
  return {
    accountId: process.env.R2_ACCOUNT_ID,
    accessKeyId: process.env.R2_ACCESS_KEY_ID,
    secretKey: process.env.R2_SECRET_ACCESS_KEY,
    bucket: process.env.R2_BUCKET_NAME,
    publicUrl: process.env.R2_PUBLIC_URL,
  };
}

function makeClient(R2: ReturnType<typeof getR2Config>) {
  return new S3Client({
    region: "auto",
    endpoint: `https://${R2.accountId}.r2.cloudflarestorage.com`,
    credentials: { accessKeyId: R2.accessKeyId!, secretAccessKey: R2.secretKey! },
  });
}

// POST — get presigned upload URL + pre-insert memories row
export async function POST(req: NextRequest) {
  const auth = await requireFamily();
  if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });

  const session = auth.session!;
  const familyId = session.familyId!;

  const R2 = getR2Config();
  if (!R2.accountId || !R2.accessKeyId || !R2.secretKey || !R2.bucket) {
    return NextResponse.json({ error: "R2 not configured" }, { status: 500 });
  }

  let body: { filename?: string; contentType?: string; childId?: string; sizeBytes?: number };
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  const { filename, contentType, childId, sizeBytes } = body;
  if (!filename || !contentType) {
    return NextResponse.json({ error: "Missing filename or contentType" }, { status: 400 });
  }

  if (!ALLOWED_TYPES.includes(contentType)) {
    return NextResponse.json({ error: "Unsupported file type" }, { status: 400 });
  }

  if (sizeBytes && sizeBytes > MAX_BYTES) {
    return NextResponse.json({ error: "File too large (max 20MB)" }, { status: 400 });
  }

  // Storage quota gate — must run before issuing the presigned URL.
  // Client-declared sizeBytes is used for the pre-check; actual size is
  // reconciled on confirm. Unknown size (0) still triggers a usage fetch
  // so families already over quota are blocked even for zero-declared uploads.
  const declaredBytes = sizeBytes ?? 0;
  const quotaCheck = await checkStorageQuota(familyId, declaredBytes);
  if (!quotaCheck.allowed) {
    return NextResponse.json(
      {
        error: quotaCheck.message,
        reason: quotaCheck.reason,
        usedBytes: quotaCheck.usedBytes,
        limitBytes: quotaCheck.limitBytes,
      },
      { status: 402 }
    );
  }

  if (childId) {
    const ownership = await requireOwnership(childId, "children", "Child");
    if (!ownership.success) {
      return NextResponse.json({ error: ownership.error }, { status: ownership.status });
    }
  }

  const ext = filename.split(".").pop()?.toLowerCase() || "jpg";
  const r2Key = `memories/${childId || familyId}/${Date.now()}-${nanoid(8)}.${ext}`;
  const baseUrl = R2.publicUrl || `https://pub-${R2.accountId}.r2.dev`;

  const client = makeClient(R2);
  const command = new PutObjectCommand({
    Bucket: R2.bucket,
    Key: r2Key,
    ContentType: contentType,
  });
  const uploadUrl = await getSignedUrl(client, command, { expiresIn: 3600 });

  // Pre-insert memories row
  const rows = await sql`
    INSERT INTO memories (family_id, child_id, r2_key, mime_type, size_bytes, uploaded_by, processing_status)
    VALUES (${familyId}, ${childId || null}, ${r2Key}, ${contentType}, ${sizeBytes || null}, ${session.userId}, 'uploading')
    RETURNING id
  `;
  const memoryId = rows[0]?.id;

  return NextResponse.json({
    uploadUrl,
    key: r2Key,
    publicUrl: `${baseUrl}/${r2Key}`,
    memoryId,
  });
}

// DELETE — remove from R2 + DB
export async function DELETE(req: NextRequest) {
  const auth = await requireFamily();
  if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });

  const familyId = auth.session!.familyId!;
  const { searchParams } = new URL(req.url);
  const key = searchParams.get("key");
  const id = searchParams.get("id");

  if (!key && !id) {
    return NextResponse.json({ error: "key or id required" }, { status: 400 });
  }

  const R2 = getR2Config();
  if (!R2.accountId || !R2.accessKeyId || !R2.secretKey || !R2.bucket) {
    return NextResponse.json({ error: "R2 not configured" }, { status: 500 });
  }

  // Resolve key from DB if only id provided
  let r2Key = key;
  let thumbKey: string | null = null;
  if (id) {
    const rows = await sql`SELECT r2_key, r2_thumbnail_key FROM memories WHERE id = ${id} AND family_id = ${familyId} LIMIT 1`;
    if (!rows[0]) return NextResponse.json({ error: "Not found" }, { status: 404 });
    r2Key = rows[0].r2_key;
    thumbKey = rows[0].r2_thumbnail_key;
  }

  const client = makeClient(R2);
  await client.send(new DeleteObjectCommand({ Bucket: R2.bucket, Key: r2Key! }));
  if (thumbKey) {
    await client.send(new DeleteObjectCommand({ Bucket: R2.bucket, Key: thumbKey })).catch(() => {});
  }

  if (id) {
    await sql`DELETE FROM memories WHERE id = ${id} AND family_id = ${familyId}`;
  }

  return NextResponse.json({ success: true });
}

// PUT — proxy upload to R2 (kept for backward compat)
export async function PUT(req: NextRequest) {
  const auth = await requireFamily();
  if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });

  const { searchParams } = new URL(req.url);
  const key = searchParams.get("key");
  const contentType = searchParams.get("contentType") || "application/octet-stream";

  if (!key) return NextResponse.json({ error: "key required" }, { status: 400 });

  const R2 = getR2Config();
  if (!R2.accountId || !R2.accessKeyId || !R2.secretKey || !R2.bucket) {
    return NextResponse.json({ error: "R2 not configured" }, { status: 500 });
  }

  const client = makeClient(R2);
  const body = await req.arrayBuffer();
  await client.send(new PutObjectCommand({
    Bucket: R2.bucket,
    Key: key,
    Body: Buffer.from(body),
    ContentType: contentType,
  }));

  return NextResponse.json({ success: true });
}

// GET — kept for backward compat (now /api/memories is primary)
export async function GET(req: NextRequest) {
  const auth = await requireFamily();
  if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });

  const familyId = auth.session!.familyId!;
  const { searchParams } = new URL(req.url);
  const childId = searchParams.get("childId");

  const rows = childId
    ? await sql`SELECT * FROM memories WHERE family_id = ${familyId} AND child_id = ${childId} ORDER BY created_at DESC`
    : await sql`SELECT * FROM memories WHERE family_id = ${familyId} ORDER BY created_at DESC`;

  const R2 = getR2Config();
  const baseUrl = R2.publicUrl || `https://pub-${R2.accountId}.r2.dev`;

  const items = rows.map(m => ({
    id: m.id,
    key: m.r2_key,
    url: `${baseUrl}/${m.r2_key}`,
    thumbnailUrl: m.r2_thumbnail_key ? `${baseUrl}/${m.r2_thumbnail_key}` : null,
    size: m.size_bytes,
    title: m.title,
    description: m.description,
    takenAt: m.taken_at,
    visionCaption: m.vision_caption,
    visionTags: m.vision_tags,
    isPrivate: m.is_private,
    processingStatus: m.processing_status,
    createdAt: m.created_at,
  }));

  return NextResponse.json({ success: true, count: items.length, items });
}
