import { NextResponse } from "next/server";
import { randomUUID } from "crypto";
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}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function isTaskType(value: unknown): value is Task["type"] {
  return typeof value === "string" && TASK_TYPES.includes(value as Task["type"]);
}

function isTaskStatus(value: unknown): value is Task["status"] {
  return typeof value === "string" && TASK_STATUSES.includes(value as Task["status"]);
}

function isTaskPriority(value: unknown): value is Task["priority"] {
  return typeof value === "string" && TASK_PRIORITIES.includes(value as Task["priority"]);
}

function isSprintStatus(value: unknown): value is Sprint["status"] {
  return typeof value === "string" && SPRINT_STATUSES.includes(value as Sprint["status"]);
}

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 getMissingColumnFromError(error: unknown): string | null {
  if (!error || typeof error !== "object") return null;
  const candidate = error as { code?: string; message?: string };
  if (candidate.code !== "PGRST204" || typeof candidate.message !== "string") return null;
  const match = candidate.message.match(/Could not find the '([^']+)' column/);
  return match?.[1] ?? null;
}

function getForeignKeyColumnFromError(error: unknown): string | null {
  if (!error || typeof error !== "object") return null;
  const candidate = error as { code?: string; details?: string };
  if (candidate.code !== "23503" || typeof candidate.details !== "string") return null;
  const match = candidate.details.match(/\(([^)]+)\)=/);
  return match?.[1] ?? null;
}

async function resolveRequiredProjectId(
  supabase: ReturnType<typeof getServiceSupabase>,
  requestedProjectId?: string
): Promise<string> {
  const requested = toNonEmptyString(requestedProjectId);
  if (requested) {
    const { data } = await supabase.from("projects").select("id").eq("id", requested).maybeSingle();
    if (data?.id) return data.id as string;
  }

  const { data: firstProject } = await supabase
    .from("projects")
    .select("id")
    .order("created_at", { ascending: true })
    .limit(1)
    .maybeSingle();

  if (firstProject?.id) return firstProject.id as string;
  throw new Error("No projects available to assign task");
}

async function resolveOptionalForeignId(
  supabase: ReturnType<typeof getServiceSupabase>,
  table: "sprints" | "users",
  id?: string
): Promise<string | null> {
  const requested = toNonEmptyString(id);
  if (!requested) return null;
  const { data } = await supabase.from(table).select("id").eq("id", requested).maybeSingle();
  return data?.id ? (data.id as string) : null;
}

function mapProjectRow(row: Record<string, unknown>): Project {
  return {
    id: String(row.id ?? ""),
    name: toNonEmptyString(row.name) ?? "Untitled Project",
    description: toNonEmptyString(row.description),
    color: toNonEmptyString(row.color) ?? "#3b82f6",
    createdAt: toNonEmptyString(row.created_at) ?? new Date().toISOString(),
  };
}

function mapSprintRow(row: Record<string, unknown>): Sprint {
  const fallbackDate = new Date().toISOString();
  return {
    id: String(row.id ?? ""),
    name: toNonEmptyString(row.name) ?? "Untitled Sprint",
    goal: toNonEmptyString(row.goal),
    startDate: toNonEmptyString(row.start_date) ?? fallbackDate,
    endDate: toNonEmptyString(row.end_date) ?? fallbackDate,
    status: isSprintStatus(row.status) ? row.status : "planning",
    projectId: String(row.project_id ?? ""),
    createdAt: toNonEmptyString(row.created_at) ?? fallbackDate,
  };
}

function mapUserRow(row: Record<string, unknown>): UserProfile | null {
  const id = toNonEmptyString(row.id);
  const name = toNonEmptyString(row.name);
  if (!id || !name) return null;
  return {
    id,
    name,
    email: toNonEmptyString(row.email),
    avatarUrl: toNonEmptyString(row.avatar_url),
  };
}

function mapTaskRow(row: Record<string, unknown>, usersById: Map<string, UserProfile>): Task {
  const fallbackDate = new Date().toISOString();
  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;

  return {
    id: String(row.id ?? ""),
    title: toNonEmptyString(row.title) ?? "",
    description: toNonEmptyString(row.description),
    type: isTaskType(row.type) ? row.type : "task",
    status: isTaskStatus(row.status) ? row.status : "todo",
    priority: isTaskPriority(row.priority) ? row.priority : "medium",
    projectId: String(row.project_id ?? ""),
    sprintId: toNonEmptyString(row.sprint_id),
    createdAt: toNonEmptyString(row.created_at) ?? fallbackDate,
    updatedAt: toNonEmptyString(row.updated_at) ?? fallbackDate,
    createdById,
    createdByName: toNonEmptyString(row.created_by_name) ?? createdByUser?.name,
    createdByAvatarUrl: toNonEmptyString(row.created_by_avatar_url) ?? createdByUser?.avatarUrl,
    updatedById,
    updatedByName: toNonEmptyString(row.updated_by_name) ?? updatedByUser?.name,
    updatedByAvatarUrl: toNonEmptyString(row.updated_by_avatar_url) ?? updatedByUser?.avatarUrl,
    assigneeId,
    assigneeName: toNonEmptyString(row.assignee_name) ?? assigneeUser?.name,
    assigneeEmail: toNonEmptyString(row.assignee_email) ?? assigneeUser?.email,
    assigneeAvatarUrl: toNonEmptyString(row.assignee_avatar_url) ?? assigneeUser?.avatarUrl,
    dueDate: toNonEmptyString(row.due_date),
    comments: Array.isArray(row.comments) ? row.comments : [],
    tags: Array.isArray(row.tags) ? row.tags.filter((tag): tag is string => typeof tag === "string") : [],
    attachments: Array.isArray(row.attachments) ? row.attachments : [],
  };
}

// GET - fetch all tasks, projects, and sprints
export async function GET() {
  try {
    const user = await getAuthenticatedUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    const supabase = getServiceSupabase();
    
    const [{ data: projects }, { data: sprints }, { data: tasks }, { data: users }, { data: meta }] = await Promise.all([
      supabase.from("projects").select("*").order("created_at", { ascending: true }),
      supabase.from("sprints").select("*").order("start_date", { ascending: true }),
      supabase.from("tasks").select("*").order("created_at", { ascending: true }),
      supabase.from("users").select("id, name, email, avatar_url"),
      supabase.from("meta").select("value").eq("key", "lastUpdated").maybeSingle(),
    ]);

    const usersById = new Map<string, UserProfile>();
    for (const row of users || []) {
      const mapped = mapUserRow(row as Record<string, unknown>);
      if (mapped) 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: (tasks || []).map((row) => mapTaskRow(row as Record<string, unknown>, usersById)),
      lastUpdated: Number(meta?.value ?? Date.now()),
    });
  } catch (error) {
    console.error(">>> API GET error:", error);
    return NextResponse.json({ error: "Failed to fetch data" }, { status: 500 });
  }
}

// POST - create or update a single task (lightweight)
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 clientTaskId = toNonEmptyString(task.id);
    const canonicalTaskId = isUuid(clientTaskId) ? clientTaskId : randomUUID();
    const resolvedProjectId = await resolveRequiredProjectId(supabase, task.projectId);
    const resolvedSprintId = await resolveOptionalForeignId(supabase, "sprints", task.sprintId);
    const resolvedAssigneeId = await resolveOptionalForeignId(supabase, "users", task.assigneeId);

    // Check if task exists
    const { data: existing } = await supabase
      .from("tasks")
      .select("id")
      .eq("id", canonicalTaskId)
      .maybeSingle();

    let taskData = stripUndefined({
      id: canonicalTaskId,
      legacy_id: clientTaskId && !isUuid(clientTaskId) ? clientTaskId : null,
      title: task.title,
      description: task.description || null,
      type: task.type || "task",
      status: task.status || "todo",
      priority: task.priority || "medium",
      project_id: resolvedProjectId,
      sprint_id: resolvedSprintId,
      created_at: existing ? undefined : (task.createdAt || now),
      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 || [],
    });

    let result;
    for (let attempt = 0; attempt < 8; attempt += 1) {
      const query = existing
        ? supabase.from("tasks").update(taskData).eq("id", canonicalTaskId).select().single()
        : supabase.from("tasks").insert(taskData).select().single();

      const { data, error } = await query;
      if (!error) {
        result = data;
        break;
      }

      const missingColumn = getMissingColumnFromError(error);
      if (missingColumn && missingColumn in taskData) {
        const nextPayload = { ...taskData };
        delete nextPayload[missingColumn];
        taskData = nextPayload;
        console.warn(`>>> API POST: removing unsupported tasks column '${missingColumn}' and retrying`);
        continue;
      }

      const foreignKeyColumn = getForeignKeyColumnFromError(error);
      if (foreignKeyColumn && foreignKeyColumn in taskData) {
        const nextPayload = { ...taskData, [foreignKeyColumn]: null };
        taskData = nextPayload;
        console.warn(`>>> API POST: clearing invalid foreign key '${foreignKeyColumn}' and retrying`);
        continue;
      }

      throw error;
    }

    if (!result) throw new Error("Failed to save task after schema fallback attempts");

    // Update lastUpdated
    await supabase.from("meta").upsert({
      key: "lastUpdated",
      value: String(Date.now()),
      updated_at: now,
    });

    return NextResponse.json({ success: true, task: result });
  } catch (error: unknown) {
    console.error(">>> API POST error:", error);
    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 supabase = getServiceSupabase();
    const { error } = await supabase.from("tasks").delete().eq("id", id);
    
    if (error) throw error;

    // Update lastUpdated
    await supabase.from("meta").upsert({
      key: "lastUpdated",
      value: String(Date.now()),
      updated_at: new Date().toISOString(),
    });

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