import 'server-only';

import { createHmac } from 'node:crypto';
import { NextResponse } from 'next/server';
import { env } from '@/env';
import { convexAuthNextjsToken } from '@convex-dev/auth/nextjs/server';
import { fetchQuery } from 'convex/nextjs';

import type { Id } from '@spoon/backend/convex/_generated/dataModel.js';
import { api } from '@spoon/backend/convex/_generated/api.js';

const terminalSecret = () =>
  env.SPOON_AGENT_TERMINAL_SECRET ??
  env.SPOON_AGENT_WORKER_INTERNAL_TOKEN ??
  env.SPOON_WORKER_TOKEN;

// Mints a short-lived, job-scoped terminal token + the worker WS URL. Returns
// null when the terminal feature is not configured. The 2-minute expiry is a
// connect window only; an established PTY session persists past it.
export const mintTerminalToken = (jobId: Id<'agentJobs'>) => {
  const secret = terminalSecret();
  const base = env.NEXT_PUBLIC_SPOON_AGENT_WORKER_WS_URL;
  if (!secret || !base) return null;
  const expiresAt = Date.now() + 2 * 60 * 1000;
  const payload = `${expiresAt}.${jobId}`;
  const signature = createHmac('sha256', secret).update(payload).digest('hex');
  const token = `${payload}.${signature}`;
  const url = `${base.replace(/\/$/, '')}/jobs/${encodeURIComponent(jobId)}/terminal?token=${encodeURIComponent(token)}`;
  return { url, expiresAt };
};

type RouteContext = {
  params: Promise<{ jobId: string }> | { jobId: string };
};

export const routeJobId = async (context: RouteContext) => {
  const params = await context.params;
  return params.jobId as Id<'agentJobs'>;
};

const workerToken = () =>
  env.SPOON_AGENT_WORKER_INTERNAL_TOKEN ?? env.SPOON_WORKER_TOKEN;

export const requireOwnedJob = async (jobId: Id<'agentJobs'>) => {
  const token = await convexAuthNextjsToken();
  if (!token) {
    return {
      ok: false as const,
      response: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }),
    };
  }
  await fetchQuery(api.agentJobs.assertOwned, { jobId }, { token });
  return { ok: true as const };
};

export const requireAuthenticatedUser = async () => {
  const token = await convexAuthNextjsToken();
  if (!token) {
    return {
      ok: false as const,
      response: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }),
    };
  }
  await fetchQuery(api.auth.getUser, {}, { token });
  return { ok: true as const };
};

export const proxyWorkerRoot = async (path: string, init?: RequestInit) => {
  const token = workerToken();
  if (!token) {
    return NextResponse.json(
      { error: 'SPOON_AGENT_WORKER_INTERNAL_TOKEN is not configured.' },
      { status: 500 },
    );
  }
  const url = new URL(path, env.SPOON_AGENT_WORKER_URL);
  const response = await fetch(url, {
    ...init,
    headers: {
      authorization: `Bearer ${token}`,
      'content-type': 'application/json',
      ...init?.headers,
    },
  });
  const text = await response.text();
  return new NextResponse(text, {
    status: response.status,
    headers: {
      'content-type':
        response.headers.get('content-type') ?? 'application/json',
    },
  });
};

export const proxyWorker = async (
  jobId: Id<'agentJobs'>,
  action: string,
  init?: RequestInit,
  search?: URLSearchParams,
) => {
  const token = workerToken();
  if (!token) {
    return NextResponse.json(
      { error: 'SPOON_AGENT_WORKER_INTERNAL_TOKEN is not configured.' },
      { status: 500 },
    );
  }
  const url = new URL(
    `/jobs/${encodeURIComponent(jobId)}/${action}`,
    env.SPOON_AGENT_WORKER_URL,
  );
  if (search) {
    for (const [key, value] of search) url.searchParams.set(key, value);
  }
  const response = await fetch(url, {
    ...init,
    headers: {
      authorization: `Bearer ${token}`,
      'content-type': 'application/json',
      ...init?.headers,
    },
  });
  const text = await response.text();
  return new NextResponse(text, {
    status: response.status,
    headers: {
      'content-type':
        response.headers.get('content-type') ?? 'application/json',
    },
  });
};

export const withOwnedJob = async (
  context: RouteContext,
  handler: (jobId: Id<'agentJobs'>) => Promise<Response>,
) => {
  try {
    const jobId = await routeJobId(context);
    const owned = await requireOwnedJob(jobId);
    if (!owned.ok) return owned.response;
    return await handler(jobId);
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    return NextResponse.json({ error: message }, { status: 500 });
  }
};
