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

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;
const BOOLEAN_TRUE_VALUES = new Set(["1", "true"]);
const BOOLEAN_FALSE_VALUES = new Set(["0", "false"]);
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 parseOptionalBoolean(value: string | null, field: string): boolean {
  if (value == null) return false;
  const normalized = value.trim().toLowerCase();
  if (BOOLEAN_TRUE_VALUES.has(normalized)) return true;
  if (BOOLEAN_FALSE_VALUES.has(normalized)) return false;
  throw new HttpError(400, `${field} must be a boolean (true/false or 1/0)`, { field, value });
}

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 requireRowString(value: unknown, field: string): string {
  if (typeof value !== "string" || value.trim().length === 0) {
    throw new HttpError(500, `${field} is missing in database row`, { field, value });
  }
  return value;
}

function serializeSprintRow(row: Record<string, unknown>, now: Date): Record<string, unknown> {
  const startDate = requireRowString(row.start_date, "sprints.start_date");
  const endDate = requireRowString(row.end_date, "sprints.end_date");

  return {
    id: row.id,
    name: row.name,
    goal: row.goal ?? null,
    start_date: startDate,
    end_date: endDate,
    status: inferSprintStatusForDateRange(startDate, endDate, now),
    created_at: row.created_at,
  };
}

// 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");
    const inProgress = parseOptionalBoolean(searchParams.get("inProgress"), "inProgress");
    const onDateInput = searchParams.get("onDate");
    if (status && !SPRINT_STATUSES.includes(status as SprintStatus)) {
      throw new HttpError(400, `status must be one of: ${SPRINT_STATUSES.join(", ")}`, { status });
    }
    const onDate = onDateInput ? toDateOnlyInput(onDateInput) : null;
    if (onDateInput && !onDate) {
      throw new HttpError(400, "onDate must be YYYY-MM-DD or ISO date-time", { onDate: onDateInput });
    }
    const filterDate = onDate || toLocalDateOnly();
    
    const supabase = getServiceSupabase();
    let query = supabase
      .from("sprints")
      .select("*")
      .order("start_date", { ascending: true });
    
    // Filter by derived status if provided (status is inferred from date range).
    if (status) {
      if (status === "planning") {
        query = query.gt("start_date", filterDate);
      } else if (status === "active") {
        query = query.lte("start_date", filterDate).gte("end_date", filterDate);
      } else {
        query = query.lt("end_date", filterDate);
      }
    }
    if (inProgress) {
      query = query.lte("start_date", filterDate).gte("end_date", filterDate);
    }
    
    const { data: sprints, error } = await query;
    
    if (error) throw error;
    
    const now = new Date();
    const serialized = (sprints || []).map((row) => serializeSprintRow(row as unknown as Record<string, unknown>, now));
    return NextResponse.json({ sprints: serialized });
  } 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 } = 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 now = new Date().toISOString();
    
    const { data, error } = await supabase
      .from("sprints")
      .insert({
        name: resolvedName,
        goal: goal || null,
        start_date: normalizedStartDate,
        end_date: normalizedEndDate,
        created_at: now,
      })
      .select()
      .single();
    
    if (error) throw error;
    
    return NextResponse.json({
      success: true,
      sprint: serializeSprintRow(data as unknown as Record<string, unknown>, new Date()),
    });
  } 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;
    }
    // Sprint status is derived from dates; ignore manual status updates.
    if (updates.status !== undefined) {
      console.warn(">>> API PATCH /sprints: ignoring updates.status because status is date-derived");
    }

    if (Object.keys(dbUpdates).length === 0) {
      const { data: existing, error: existingError } = await supabase
        .from("sprints")
        .select("*")
        .eq("id", sprintId)
        .single();

      if (existingError) throw existingError;

      return NextResponse.json({
        success: true,
        sprint: serializeSprintRow(existing as unknown as Record<string, unknown>, new Date()),
      });
    }
    
    const { data, error } = await supabase
      .from("sprints")
      .update(dbUpdates)
      .eq("id", sprintId)
      .select()
      .single();
    
    if (error) throw error;
    
    return NextResponse.json({
      success: true,
      sprint: serializeSprintRow(data as unknown as Record<string, unknown>, new Date()),
    });
  } 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 });
  }
}
