import { NextRequest, NextResponse } from 'next/server';
import { prisma, prismaAuth } from '@/lib/db';
import { auth } from '@/app/api/auth/[...nextauth]/route';

const MAX_TAG_NAME_LENGTH = 100;
const MAX_NOTES_LENGTH = 500;
const MAX_CUSTOM_TAGS = 10;

interface TagLinkagePayload {
  photoIds?: number[];
  tagIds?: number[];
  newTagName?: string;
  newTagNames?: string[];
  notes?: string;
}

function parseIds(values: unknown, label: string) {
  if (!Array.isArray(values)) {
    throw new Error(`${label} must be an array`);
  }

  const parsed = Array.from(
    new Set(
      values.map((value) => {
        const num = Number(value);
        if (!Number.isInteger(num) || num <= 0) {
          throw new Error(`${label} must contain positive integers`);
        }
        return num;
      })
    )
  );

  if (parsed.length === 0) {
    throw new Error(`${label} cannot be empty`);
  }

  return parsed;
}

export async function POST(request: NextRequest) {
  try {
    const session = await auth();
    if (!session?.user?.id) {
      return NextResponse.json(
        { error: 'Authentication required. Please sign in to tag photos.' },
        { status: 401 }
      );
    }

    const userId = parseInt(session.user.id, 10);
    if (!Number.isInteger(userId)) {
      return NextResponse.json(
        { error: 'Invalid user session' },
        { status: 401 }
      );
    }

    let payload: TagLinkagePayload;
    try {
      payload = await request.json();
    } catch (error: any) {
      if (error?.type === 'invalid-json') {
        return NextResponse.json({ error: 'Invalid JSON payload' }, { status: 400 });
      }
      throw error;
    }

    if (!payload || typeof payload !== 'object') {
      return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
    }

    if (!payload.photoIds) {
      return NextResponse.json({ error: 'photoIds are required' }, { status: 400 });
    }

    const photoIds = parseIds(payload.photoIds, 'photoIds');
    const tagIds =
      Array.isArray(payload.tagIds) && payload.tagIds.length > 0
        ? parseIds(payload.tagIds, 'tagIds')
        : [];

    const incomingNewNames: string[] = [];
    if (Array.isArray(payload.newTagNames)) {
      incomingNewNames.push(...payload.newTagNames);
    }
    if (typeof payload.newTagName === 'string') {
      incomingNewNames.push(payload.newTagName);
    }

    const newNameMap = new Map<string, string>();
    for (const rawName of incomingNewNames) {
      if (typeof rawName !== 'string') continue;
      const trimmed = rawName.trim().replace(/\s+/g, ' ');
      if (trimmed.length === 0) continue;
      if (trimmed.length > MAX_TAG_NAME_LENGTH) {
        return NextResponse.json(
          { error: `Tag name must be ${MAX_TAG_NAME_LENGTH} characters or less` },
          { status: 400 }
        );
      }
      const key = trimmed.toLowerCase();
      if (!newNameMap.has(key)) {
        newNameMap.set(key, trimmed);
      }
    }
    const normalizedNewNames = Array.from(newNameMap.values());

    if (normalizedNewNames.length > MAX_CUSTOM_TAGS) {
      return NextResponse.json(
        { error: `You can submit up to ${MAX_CUSTOM_TAGS} new tag names at a time` },
        { status: 400 }
      );
    }

    if (tagIds.length === 0 && normalizedNewNames.length === 0) {
      return NextResponse.json(
        { error: 'Select at least one existing tag or provide a new tag name' },
        { status: 400 }
      );
    }

    let notes: string | null = null;
    if (typeof payload.notes === 'string') {
      notes = payload.notes.trim();
      if (notes.length > MAX_NOTES_LENGTH) {
        return NextResponse.json(
          { error: `Notes must be ${MAX_NOTES_LENGTH} characters or less` },
          { status: 400 }
        );
      }
      if (notes.length === 0) {
        notes = null;
      }
    }

    // Ensure all photos exist
    const photos = await prisma.photo.findMany({
      where: { id: { in: photoIds } },
      select: { id: true },
    });

    if (photos.length !== photoIds.length) {
      return NextResponse.json(
        { error: 'One or more photos were not found' },
        { status: 404 }
      );
    }

    // Ensure all tag IDs exist
    if (tagIds.length > 0) {
      const tags = await prisma.tag.findMany({
        where: { id: { in: tagIds } },
        select: { id: true },
      });

      if (tags.length !== tagIds.length) {
        return NextResponse.json(
          { error: 'One or more tags were not found' },
          { status: 404 }
        );
      }
    }

    // Skip combinations that are already applied
    let appliedCombinationKeys = new Set<string>();
    if (tagIds.length > 0) {
      const applied = await prisma.photoTagLinkage.findMany({
        where: {
          photo_id: { in: photoIds },
          tag_id: { in: tagIds },
        },
        select: {
          photo_id: true,
          tag_id: true,
        },
      });

      appliedCombinationKeys = new Set(
        applied.map((link) => `${link.photo_id}:${link.tag_id}`)
      );
    }

    // Skip combinations that are already pending for the same user
    const pendingConditions: any[] = [];
    if (tagIds.length > 0) {
      pendingConditions.push({
        photoId: { in: photoIds },
        tagId: { in: tagIds },
      });
    }
    normalizedNewNames.forEach((name) => {
      pendingConditions.push({
        photoId: { in: photoIds },
        tagName: name,
      });
    });

    let pendingCombinationKeys = new Set<string>();
    if (pendingConditions.length > 0) {
      const existingPending = await prismaAuth.pendingLinkage.findMany({
        where: {
          userId,
          status: 'pending',
          OR: pendingConditions,
        },
        select: {
          photoId: true,
          tagId: true,
          tagName: true,
        },
      });

      pendingCombinationKeys = new Set(
        existingPending.map((item) =>
          item.tagId
            ? `${item.photoId}:${item.tagId}`
            : `${item.photoId}:name:${item.tagName?.toLowerCase()}`
        )
      );
    }

    type PendingEntry = { photoId: number; tagId?: number; tagName?: string | null };
    const entries: PendingEntry[] = [];

    for (const photoId of photoIds) {
      for (const tagId of tagIds) {
        const key = `${photoId}:${tagId}`;
        if (appliedCombinationKeys.has(key)) {
          continue;
        }
        if (pendingCombinationKeys.has(key)) {
          continue;
        }
        entries.push({ photoId, tagId });
      }

      for (const tagName of normalizedNewNames) {
        const key = `${photoId}:name:${tagName.toLowerCase()}`;
        if (!pendingCombinationKeys.has(key)) {
          entries.push({ photoId, tagName });
        }
      }
    }

    if (entries.length === 0) {
      return NextResponse.json(
        { error: 'Selected tag linkages are already applied or pending' },
        { status: 409 }
      );
    }

    await prismaAuth.pendingLinkage.createMany({
      data: entries.map((entry) => ({
        photoId: entry.photoId,
        tagId: entry.tagId ?? null,
        tagName: entry.tagName ?? null,
        userId,
        status: 'pending',
        notes,
      })),
    });

    return NextResponse.json({
      message: 'Tags were submitted and are pending approval',
      pendingCount: entries.length,
    });
  } catch (error: any) {
    console.error('Error submitting tag linkages:', error);
    if (error.message?.includes('must')) {
      return NextResponse.json({ error: error.message }, { status: 400 });
    }
    return NextResponse.json(
      { error: 'Failed to submit tag linkages', details: error.message },
      { status: 500 }
    );
  }
}

