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

export const runtime = "nodejs";

interface Task {
  id: string;
  title: string;
  description?: string;
  type: "idea" | "task" | "bug" | "research" | "plan";
  status: "open" | "todo" | "blocked" | "in-progress" | "review" | "validate" | "archived" | "canceled" | "done";
  priority: "low" | "medium" | "high" | "urgent";
  projectId: string;
  sprintId?: string;
  createdAt?: string;
  updatedAt?: string;
  createdById?: string;
  createdByName?: string;
  createdByAvatarUrl?: string;
  updatedById?: string;
  updatedByName?: string;
  updatedByAvatarUrl?: string;
  assigneeId?: string;
  assigneeName?: string;
  assigneeEmail?: string;
  assigneeAvatarUrl?: string;
  dueDate?: string;
  comments?: unknown[];
  tags?: string[];
  attachments?: unknown[];
}

interface Project {
  id: string;
  name: string;
  description?: string;
  color: string;
  createdAt: string;
}

interface Sprint {
  id: string;
  name: string;
  goal?: string;
  startDate: string;
  endDate: string;
  status: "planning" | "active" | "completed";
  projectId: string;
  createdAt: string;
}

interface UserProfile {
  id: string;
  name: string;
  email?: string;
  avatarUrl?: string;
}

const TASK_TYPES: Task["type"][] = ["idea", "task", "bug", "research", "plan"];
const TASK_STATUSES: Task["status"][] = ["open", "todo", "blocked", "in-progress", "review", "validate", "archived", "canceled", "done"];
const TASK_PRIORITIES: Task["priority"][] = ["low", "medium", "high", "urgent"];
const SPRINT_STATUSES: Sprint["status"][] = ["planning", "active", "completed"];
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

// Optimized field selection - only fetch fields needed for board display
// Full task details (description, comments, attachments) fetched lazily
const TASK_FIELDS_LIGHT = [
  "id",
  "title",
  "type",
  "status",
  "priority",
  "project_id",
  "sprint_id",
  "created_at",
  "updated_at",
  "created_by_id",
  "updated_by_id",
  "assignee_id",
  "due_date",
  "tags",
];

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 toErrorDetails(error: unknown): Record<string, unknown> | undefined {
  if (!error || typeof error !== "object") return undefined;
  const candidate = error as { code?: unknown; details?: unknown; hint?: unknown; message?: unknown };
  return {
    code: candidate.code,
    details: candidate.details,
    hint: candidate.hint,
    message: candidate.message,
  };
}

function throwQueryError(scope: string, error: unknown): never {
  throw new HttpError(500, `${scope} query failed`, {
    scope,
    ...toErrorDetails(error),
  });
}

function toNonEmptyString(value: unknown): string | undefined {
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
}

function isUuid(value: string | undefined): value is string {
  return typeof value === "string" && UUID_PATTERN.test(value);
}

function stripUndefined<T extends Record<string, unknown>>(value: T): Record<string, unknown> {
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
}

function requireNonEmptyString(value: unknown, field: string, status = 400): string {
  const normalized = toNonEmptyString(value);
  if (!normalized) {
    throw new HttpError(status, `${field} is required`, { field, value });
  }
  return normalized;
}

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

function requireEnum<T extends string>(value: unknown, allowed: readonly T[], field: string, status = 400): T {
  if (typeof value !== "string" || !allowed.includes(value as T)) {
    throw new HttpError(status, `Invalid ${field} value`, { field, value });
  }
  return value as T;
}

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

async function resolveOptionalForeignId(
  supabase: ReturnType<typeof getServiceSupabase>,
  table: "sprints" | "users",
  id: string | undefined,
  field: string
): Promise<string | null> {
  const requested = toNonEmptyString(id);
  if (!requested) return null;
  if (!isUuid(requested)) {
    throw new HttpError(400, `${field} must be a UUID when provided`, { field, value: id });
  }

  const { data, error } = await supabase
    .from(table)
    .select("id")
    .eq("id", requested)
    .maybeSingle();
  if (error) throw error;
  if (!data?.id) {
    throw new HttpError(400, `${field} does not exist`, { field, value: requested });
  }
  return data.id as string;
}

function mapProjectRow(row: Record<string, unknown>): Project {
  return {
    id: requireNonEmptyString(row.id, "projects.id", 500),
    name: requireNonEmptyString(row.name, "projects.name", 500),
    description: toNonEmptyString(row.description),
    color: requireNonEmptyString(row.color, "projects.color", 500),
    createdAt: requireNonEmptyString(row.created_at, "projects.created_at", 500),
  };
}

function mapSprintRow(row: Record<string, unknown>): Sprint {
  return {
    id: requireNonEmptyString(row.id, "sprints.id", 500),
    name: requireNonEmptyString(row.name, "sprints.name", 500),
    goal: toNonEmptyString(row.goal),
    startDate: requireNonEmptyString(row.start_date, "sprints.start_date", 500),
    endDate: requireNonEmptyString(row.end_date, "sprints.end_date", 500),
    status: requireEnum(row.status, SPRINT_STATUSES, "sprints.status", 500),
    projectId: requireNonEmptyString(row.project_id, "sprints.project_id", 500),
    createdAt: requireNonEmptyString(row.created_at, "sprints.created_at", 500),
  };
}

function mapUserRow(row: Record<string, unknown>): UserProfile {
  const id = requireNonEmptyString(row.id, "users.id", 500);
  const name = requireNonEmptyString(row.name, "users.name", 500);
  return {
    id,
    name,
    email: toNonEmptyString(row.email),
    avatarUrl: toNonEmptyString(row.avatar_url),
  };
}

function mapTaskRow(row: Record<string, unknown>, usersById: Map<string, UserProfile>, includeFullData = false): Task {
  const createdById = toNonEmptyString(row.created_by_id);
  const updatedById = toNonEmptyString(row.updated_by_id);
  const assigneeId = toNonEmptyString(row.assignee_id);
  const createdByUser = createdById ? usersById.get(createdById) : undefined;
  const updatedByUser = updatedById ? usersById.get(updatedById) : undefined;
  const assigneeUser = assigneeId ? usersById.get(assigneeId) : undefined;

  if (!Array.isArray(row.tags)) {
    throw new HttpError(500, "Invalid tasks.tags value in database", { taskId: row.id, value: row.tags });
  }

  if (includeFullData && row.comments !== undefined && !Array.isArray(row.comments)) {
    throw new HttpError(500, "Invalid tasks.comments value in database", { taskId: row.id, value: row.comments });
  }

  if (includeFullData && row.attachments !== undefined && !Array.isArray(row.attachments)) {
    throw new HttpError(500, "Invalid tasks.attachments value in database", { taskId: row.id, value: row.attachments });
  }

  const task: Task = {
    id: requireNonEmptyString(row.id, "tasks.id", 500),
    title: requireNonEmptyString(row.title, "tasks.title", 500),
    description: includeFullData ? toNonEmptyString(row.description) : undefined,
    type: requireEnum(row.type, TASK_TYPES, "tasks.type", 500),
    status: requireEnum(row.status, TASK_STATUSES, "tasks.status", 500),
    priority: requireEnum(row.priority, TASK_PRIORITIES, "tasks.priority", 500),
    projectId: requireNonEmptyString(row.project_id, "tasks.project_id", 500),
    sprintId: toNonEmptyString(row.sprint_id),
    createdAt: requireNonEmptyString(row.created_at, "tasks.created_at", 500),
    updatedAt: requireNonEmptyString(row.updated_at, "tasks.updated_at", 500),
    createdById,
    createdByName: createdByUser?.name,
    createdByAvatarUrl: createdByUser?.avatarUrl,
    updatedById,
    updatedByName: updatedByUser?.name,
    updatedByAvatarUrl: updatedByUser?.avatarUrl,
    assigneeId,
    assigneeName: assigneeUser?.name,
    assigneeEmail: assigneeUser?.email,
    assigneeAvatarUrl: assigneeUser?.avatarUrl,
    dueDate: toNonEmptyString(row.due_date),
    comments: includeFullData ? (row.comments as unknown[] | undefined) ?? [] : [],
    tags: (row.tags as unknown[]).filter((tag): tag is string => typeof tag === "string"),
    attachments: includeFullData ? (row.attachments as unknown[] | undefined) ?? [] : [],
  };

  return task;
}

// GET - fetch all tasks, projects, and sprints
// Uses lightweight fields for faster initial load
export async function GET() {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    const supabase = getServiceSupabase();
    
    // Use Promise.all for parallel queries with optimized field selection
    const [
      { data: projects, error: projectsError },
      { data: sprints, error: sprintsError },
      { data: taskRows, error: tasksError },
      { data: users, error: usersError }
    ] = await Promise.all([
      supabase.from("projects").select("id, name, description, color, created_at").order("created_at", { ascending: true }),
      supabase.from("sprints").select("id, name, goal, start_date, end_date, status, project_id, created_at").order("start_date", { ascending: true }),
      supabase.from("tasks").select(TASK_FIELDS_LIGHT.join(", ")).order("updated_at", { ascending: false }),
      supabase.from("users").select("id, name, email, avatar_url"),
    ]);

    if (projectsError) throwQueryError("projects", projectsError);
    if (sprintsError) throwQueryError("sprints", sprintsError);
    if (tasksError) throwQueryError("tasks", tasksError);
    if (usersError) throwQueryError("users", usersError);

    const usersById = new Map<string, UserProfile>();
    for (const row of users || []) {
      const mapped = mapUserRow(row as Record<string, unknown>);
      usersById.set(mapped.id, mapped);
    }
    
    return NextResponse.json({
      projects: (projects || []).map((row) => mapProjectRow(row as Record<string, unknown>)),
      sprints: (sprints || []).map((row) => mapSprintRow(row as Record<string, unknown>)),
      tasks: ((taskRows as unknown as Record<string, unknown>[] | null) || []).map((row) => mapTaskRow(row, usersById, false)),
      currentUser: {
        id: user.id,
        name: user.name,
        email: user.email,
        avatarUrl: user.avatarUrl,
      },
      lastUpdated: Date.now(),
    }, {
      headers: {
        'Cache-Control': 'no-store',
      }
    });
  } catch (error) {
    console.error(">>> API GET error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    const message = error instanceof Error ? error.message : "Failed to fetch data";
    return NextResponse.json({ error: message, details: toErrorDetails(error) }, { status: 500 });
  }
}

// POST - create or update a single task
export async function POST(request: Request) {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      console.error(">>> API POST: No authenticated user");
      return NextResponse.json({ error: "Unauthorized - please log in again" }, { status: 401 });
    }
    console.log(">>> API POST: Authenticated as", user.email);

    const body = await request.json();
    const { task } = body as { task?: Task };

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

    const supabase = getServiceSupabase();
    const now = new Date().toISOString();
    const taskId = requireUuid(task.id, "task.id");
    const resolvedProjectId = await resolveRequiredProjectId(supabase, task.projectId);
    const resolvedSprintId = await resolveOptionalForeignId(supabase, "sprints", task.sprintId, "task.sprintId");
    const resolvedAssigneeId = await resolveOptionalForeignId(supabase, "users", task.assigneeId, "task.assigneeId");

    // Check if task exists
    let existing: { id: string } | null = null;
    const { data: byId, error: byIdError } = await supabase
      .from("tasks")
      .select("id")
      .eq("id", taskId)
      .maybeSingle();
    if (byIdError) throw byIdError;
    existing = (byId as { id: string } | null) ?? null;

    const taskData = stripUndefined({
      id: taskId,
      title: requireNonEmptyString(task.title, "task.title"),
      description: task.description || null,
      type: task.type ? requireEnum(task.type, TASK_TYPES, "task.type") : "task",
      status: task.status ? requireEnum(task.status, TASK_STATUSES, "task.status") : "todo",
      priority: task.priority ? requireEnum(task.priority, TASK_PRIORITIES, "task.priority") : "medium",
      project_id: resolvedProjectId,
      sprint_id: resolvedSprintId,
      created_at: existing ? undefined : toNonEmptyString(task.createdAt),
      updated_at: now,
      created_by_id: existing ? undefined : user.id,
      updated_by_id: user.id,
      assignee_id: resolvedAssigneeId,
      due_date: task.dueDate || null,
      comments: task.comments || [],
      tags: task.tags || [],
      attachments: task.attachments || [],
    });

    const query = existing
      ? supabase.from("tasks").update(taskData).eq("id", taskId).select().single()
      : supabase.from("tasks").insert(taskData).select().single();
    const { data: result, error: saveError } = await query;
    if (saveError) throw saveError;
    if (!result) throw new HttpError(500, "Task save succeeded without returning a row");

    return NextResponse.json({ success: true, task: result });
  } catch (error: unknown) {
    console.error(">>> API POST error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    const message = error instanceof Error ? error.message : "Failed to save";
    const details =
      error && typeof error === "object"
        ? {
            code: "code" in error ? (error as { code?: unknown }).code : undefined,
            details: "details" in error ? (error as { details?: unknown }).details : undefined,
            hint: "hint" in error ? (error as { hint?: unknown }).hint : undefined,
          }
        : undefined;
    return NextResponse.json({ error: message, details }, { status: 500 });
  }
}

// DELETE - remove a task
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()) as { id: string };
    const taskId = requireUuid(id, "id");
    
    const supabase = getServiceSupabase();
    const { error, count } = await supabase
      .from("tasks")
      .delete({ count: "exact" })
      .eq("id", taskId);
    
    if (error) throw error;
    if ((count ?? 0) === 0) {
      throw new HttpError(404, "Task not found", { id: taskId });
    }

    return NextResponse.json({ success: true });
  } catch (error: unknown) {
    console.error(">>> API DELETE error:", error);
    if (error instanceof HttpError) {
      return NextResponse.json({ error: error.message, details: error.details }, { status: error.status });
    }
    const message = error instanceof Error ? error.message : "Failed to delete";
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
