// lib/api.ts — shared types and fetch helpers for the SDP dashboard.

// Slice-2: the dashboard is served under a Next.js basePath
// (default /sandbox/credit-card, see next.config.js). All
// fetch() URLs must be prefixed with it so /api/ and /ws/ resolve
// against the dashboard's mounted path, not the host root.
// NEXT_PUBLIC_BASE_PATH is set in next.config.js (build-time env) so
// the value is baked into the static bundle.
const BASE = (process.env.NEXT_PUBLIC_BASE_PATH ?? '').replace(/\/$/, '')

function p(path: string): string {
  if (!BASE) return path
  if (path.startsWith('http://') || path.startsWith('https://')) return path
  return BASE + path
}

export type Repo = { name: string; node: string; path: string; defaultBranch?: string }

export type Deployment = {
  id: string
  sandboxId?: string
  repository: string
  branch: string
  user: string
  state: string
  containerId?: string
  hostPort?: number
  startedAt: number
  completedAt?: number
}

export type Environment = {
  id: string
  name: string
  values: Record<string, string>
  createdAt: number
  updatedAt: number
}

export type SandboxService = {
  id: string
  sandboxId: string
  repo: string
  branch?: string
  envId?: string
  hostPort: number
  useOcp: boolean
}

export type Sandbox = {
  id: string
  name: string
  gatewayBranch?: string
  gatewayEnvId?: string
  gatewayHostPort?: number
  createdAt: number
  updatedAt: number
  services: SandboxService[]
}

export type TemplateService = SandboxService

export type Template = {
  id: string
  name: string
  gatewayBranch?: string
  createdAt: number
  updatedAt: number
  services: TemplateService[]
}

export type Route = {
  id: string
  sandboxId: string
  key: string
  value: string
  targetOcp: boolean
}

// RoutePush is the body for POST /api/routes/push — the agent fills
// in id and sandboxId from the request context.
export type RoutePush = {
  key: string
  value: string
  targetOcp: boolean
}

async function jget<T>(url: string): Promise<T> {
  const r = await fetch(url, { credentials: 'include' })
  if (!r.ok) {
    const t = await r.text()
    throw new Error(t || `${url} failed (${r.status})`)
  }
  return r.json()
}

async function jsend<T>(url: string, method: string, body?: unknown): Promise<T> {
  const r = await fetch(url, {
    method,
    headers: { 'content-type': 'application/json' },
    credentials: 'include',
    body: body !== undefined ? JSON.stringify(body) : undefined,
  })
  if (!r.ok) {
    const t = await r.text()
    throw new Error(t || `${url} failed (${r.status})`)
  }
  if (r.status === 204) return undefined as T
  return r.json()
}

// --- repos / agents ---

export function listRepos() {
  return jget<Repo[]>(p('/api/repos'))
}

export function listBranches(repo: string) {
  return jget<string[]>(p(`/api/repos/branches?repo=${encodeURIComponent(repo)}`))
}

export function listAgents() {
  return jget<{ node: string }[]>(p('/api/agents'))
}

// --- deployments ---

export function startDeploy(payload: {
  repository: string
  branch: string
  username: string
  password: string
  env?: Record<string, string>
  sandboxId?: string
  hostPort?: number
}) {
  return jsend<{ id: string }>(p('/api/deployments/new'), 'POST', payload)
}

export function stopDeployment(id: string, node: string) {
  return jsend<{ ok: boolean }>(p('/api/deployments/stop'), 'POST', { id, node })
}

export function listDeployments(sandboxID?: string) {
  const q = sandboxID ? `?sandbox=${encodeURIComponent(sandboxID)}` : ''
  return jget<Deployment[]>(p(`/api/deployments${q}`))
}

// --- environments ---

export function listEnvironments() {
  return jget<Environment[]>(p('/api/environments'))
}

export function createEnvironment(name: string, values: Record<string, string>) {
  return jsend<Environment>(p('/api/environments'), 'POST', { name, values })
}

export function updateEnvironment(id: string, body: Partial<{ name: string; values: Record<string, string> }>) {
  return jsend<Environment>(p(`/api/environments/${id}`), 'PUT', body)
}

export function deleteEnvironment(id: string) {
  return jsend<{ ok: boolean }>(p(`/api/environments/${id}`), 'DELETE')
}

// --- sandboxes ---

export function listSandboxes() {
  return jget<Sandbox[]>(p('/api/sandboxes'))
}

export function getSandbox(id: string) {
  return jget<Sandbox>(p(`/api/sandboxes/${id}`))
}

export function createSandbox(body: Partial<Sandbox>) {
  return jsend<Sandbox>(p('/api/sandboxes'), 'POST', body)
}

export function updateSandbox(id: string, body: Partial<Sandbox>) {
  return jsend<Sandbox>(p(`/api/sandboxes/${id}`), 'PUT', body)
}

export function deleteSandbox(id: string) {
  return jsend<{ ok: boolean }>(p(`/api/sandboxes/${id}`), 'DELETE')
}

export function cloneSandbox(templateID: string, name: string) {
  return jsend<Sandbox>(p('/api/sandboxes/clone'), 'POST', { templateId: templateID, name })
}

export function deploySandboxService(sandboxID: string, repo: string, body: {
  branch: string
  username: string
  password: string
  env?: Record<string, string>
  envId?: string
}) {
  return jsend<{ id: string }>(p(`/api/sandboxes/${sandboxID}/deploy/${repo}`), 'POST', body)
}

// --- templates ---

export function listTemplates() {
  return jget<Template[]>(p('/api/templates'))
}

export function getTemplate(id: string) {
  return jget<Template>(p(`/api/templates/${id}`))
}

export function createTemplate(body: Partial<Template>) {
  return jsend<Template>(p('/api/templates'), 'POST', body)
}

export function updateTemplate(id: string, body: Partial<Template>) {
  return jsend<Template>(p(`/api/templates/${id}`), 'PUT', body)
}

export function deleteTemplate(id: string) {
  return jsend<{ ok: boolean }>(p(`/api/templates/${id}`), 'DELETE')
}

// --- routes ---

// LiveRoute is the live <key>_url line from the gateway's config.php,
// plus the dashboard's view of whether it's currently OCP or
// overridden.
export type LiveRoute = {
  key: string                 // e.g. "haven"
  url: string                 // current value in the file
  ocpDefault: string | null   // snapshot value for the current branch
  overridden: boolean         // true if some sandbox has an active override
  sandboxId?: string          // which sandbox owns the override
  overrideValue?: string      // the override's value (when overridden)
  targetOcp?: boolean         // true if the override is "restore to OCP"
}

export type LiveRoutesResponse = {
  branch: string
  routes: Record<string, string>      // live map from agent
  defaults: Record<string, string>    // per-branch OCP snapshot
  overrides: Record<string, Record<string, { key: string; value: string; targetOcp: boolean }>>
  configPath: string
}

export function listRoutes() {
  return jget<LiveRoutesResponse>(p('/api/routes'))
}

export function pushRoutes(sandboxID: string, routes: RoutePush[]) {
  return jsend<{ ok: boolean }>(p('/api/routes/push'), 'POST', { sandboxId: sandboxID, routes })
}
