import { NextResponse } from "next/server";
import { getServiceSupabase } from "@/lib/supabase/client";
import { getAuthenticatedUser } from "@/lib/server/auth";

export const runtime = "nodejs";

// Sprint dates are stored as SQL DATE values (YYYY-MM-DD). We accept either
// date-only or ISO datetime inputs and normalize to the date prefix.
const DATE_PREFIX_PATTERN = /^(\d{4}-\d{2}-\d{2})/;
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const SPRINT_STATUSES = ["planning", "active", "completed"] as const;
type SprintStatus = (typeof SPRINT_STATUSES)[number];

class HttpError extends Error {
  readonly status: number;
  readonly details?: Record<string, unknown>;

  constructor(status: number, message: string, details?: Record<string, unknown>) {
    super(message);
    this.status = status;
    this.details = details;
  }
}

function toDateOnlyInput(value: unknown): string | null {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  if (!trimmed) return null;
  const match = trimmed.match(DATE_PREFIX_PATTERN);
  return match?.[1] ?? null;
}

function requireNonEmptyString(value: unknown, field: string): string {
  if (typeof value !== "string" || value.trim().length === 0) {
    throw new HttpError(400, `${field} is required`, { field, value });
  }
  return value.trim();
}

function requireUuid(value: unknown, field: string): string {
  const normalized = requireNonEmptyString(value, field);
  if (!UUID_PATTERN.test(normalized)) {
    throw new HttpError(400, `${field} must be a UUID`, { field, value });
  }
  return normalized;
}

function requireSprintStatus(value: unknown, field: string): SprintStatus {
  if (typeof value !== "string" || !SPRINT_STATUSES.includes(value as SprintStatus)) {
    throw new HttpError(400, `${field} must be one of: ${SPRINT_STATUSES.join(", ")}`, { field, value });
  }
  return value as SprintStatus;
}

async function requireExistingProjectId(
  supabase: ReturnType<typeof getServiceSupabase>,
  projectId: string
): Promise<string> {
  const { data, error } = await supabase.from("projects").select("id").eq("id", projectId).maybeSingle();
  if (error) throw error;
  if (!data?.id) {
    throw new HttpError(400, "projectId does not exist", { projectId });
  }
  return data.id;
}

// GET - fetch all sprints (optionally filtered by status)
export async function GET(request: Request) {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    // Parse query params
    const { searchParams } = new URL(request.url);
    const status = searchParams.get("status");
    if (status && !SPRINT_STATUSES.includes(status as SprintStatus)) {
      throw new HttpError(400, `status must be one of: ${SPRINT_STATUSES.join(", ")}`, { status });
    }
    
    const supabase = getServiceSupabase();
    let query = supabase
      .from("sprints")
      .select("*")
      .order("start_date", { ascending: true });
    
    // Filter by status if provided
    if (status) {
      query = query.eq("status", status);
    }
    
    const { data: sprints, error } = await query;
    
    if (error) throw error;
    
    return NextResponse.json({ sprints: sprints || [] });
  } catch (error) {
    console.error(">>> API GET /sprints error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    return NextResponse.json({ error: "Failed to fetch sprints" }, { status: 500 });
  }
}

// POST - create a new sprint
export async function POST(request: Request) {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    const body = await request.json();
    const { name, goal, startDate, endDate, status, projectId } = body;
    
    const supabase = getServiceSupabase();
    const resolvedName = requireNonEmptyString(name, "name");
    const normalizedStartDate = toDateOnlyInput(startDate);
    if (!normalizedStartDate) {
      throw new HttpError(400, "startDate must be YYYY-MM-DD or ISO date-time", { startDate });
    }
    const normalizedEndDate = toDateOnlyInput(endDate);
    if (!normalizedEndDate) {
      throw new HttpError(400, "endDate must be YYYY-MM-DD or ISO date-time", { endDate });
    }
    const resolvedStatus = requireSprintStatus(status, "status");
    const resolvedProjectId = await requireExistingProjectId(
      supabase,
      requireUuid(projectId, "projectId")
    );
    const now = new Date().toISOString();
    
    const { data, error } = await supabase
      .from("sprints")
      .insert({
        name: resolvedName,
        goal: goal || null,
        start_date: normalizedStartDate,
        end_date: normalizedEndDate,
        status: resolvedStatus,
        project_id: resolvedProjectId,
        created_at: now,
      })
      .select()
      .single();
    
    if (error) throw error;
    
    return NextResponse.json({ success: true, sprint: data });
  } catch (error) {
    console.error(">>> API POST /sprints error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    return NextResponse.json({ error: "Failed to create sprint" }, { status: 500 });
  }
}

// PATCH - update a sprint
export async function PATCH(request: Request) {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    const body = await request.json();
    const { id, ...updates } = body;
    
    const supabase = getServiceSupabase();
    const sprintId = requireUuid(id, "id");
    
    // Map camelCase to snake_case for database and keep date-only semantics.
    const dbUpdates: Record<string, unknown> = {};
    if (updates.name !== undefined) dbUpdates.name = requireNonEmptyString(updates.name, "name");
    if (updates.goal !== undefined) dbUpdates.goal = updates.goal;
    if (updates.startDate !== undefined) {
      const normalizedStartDate = toDateOnlyInput(updates.startDate);
      if (!normalizedStartDate) {
        throw new HttpError(400, "startDate must be YYYY-MM-DD or ISO date-time", { startDate: updates.startDate });
      }
      dbUpdates.start_date = normalizedStartDate;
    }
    if (updates.endDate !== undefined) {
      const normalizedEndDate = toDateOnlyInput(updates.endDate);
      if (!normalizedEndDate) {
        throw new HttpError(400, "endDate must be YYYY-MM-DD or ISO date-time", { endDate: updates.endDate });
      }
      dbUpdates.end_date = normalizedEndDate;
    }
    if (updates.status !== undefined) dbUpdates.status = requireSprintStatus(updates.status, "status");
    if (updates.projectId !== undefined) {
      const projectId = requireUuid(updates.projectId, "projectId");
      dbUpdates.project_id = await requireExistingProjectId(supabase, projectId);
    }
    
    const { data, error } = await supabase
      .from("sprints")
      .update(dbUpdates)
      .eq("id", sprintId)
      .select()
      .single();
    
    if (error) throw error;
    
    return NextResponse.json({ success: true, sprint: data });
  } catch (error) {
    console.error(">>> API PATCH /sprints error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    return NextResponse.json({ error: "Failed to update sprint" }, { status: 500 });
  }
}

// DELETE - delete a sprint
export async function DELETE(request: Request) {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    const { id } = await request.json();
    const sprintId = requireUuid(id, "id");
    
    const supabase = getServiceSupabase();
    const { error, count } = await supabase
      .from("sprints")
      .delete({ count: "exact" })
      .eq("id", sprintId);
    
    if (error) throw error;
    if ((count ?? 0) === 0) {
      throw new HttpError(404, "Sprint not found", { id: sprintId });
    }
    
    return NextResponse.json({ success: true });
  } catch (error) {
    console.error(">>> API DELETE /sprints error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    return NextResponse.json({ error: "Failed to delete sprint" }, { status: 500 });
  }
}
