/**
 * GitHub OAuth integration helpers.
 *
 * Storage layout — fs_users.data.integrations.github:
 *   {
 *     login:        "octocat",
 *     accessToken:  <encryptSecret(token)>,    // encrypted at rest
 *     scope:        "repo,read:user",
 *     connectedAt:  ISO,
 *   }
 *
 * Tokens never leave the server. All API calls happen server-side; the
 * UI only ever sees the GitHub `login` and the list of repos.
 *
 * Scopes requested: `repo` (private+public read+write so we can mirror)
 *                   `read:user` (so we can show the connected username)
 */

import { encryptSecret, decryptSecret } from "@/lib/auth/secret-box";
import { query } from "@/lib/db-postgres";

const CLIENT_ID = process.env.GITHUB_CLIENT_ID ?? "";
const CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET ?? "";
const SCOPES = "repo,read:user";

export function isGithubOauthConfigured(): boolean {
  return CLIENT_ID.length > 0 && CLIENT_SECRET.length > 0;
}

/** Build the GitHub authorize URL. `state` MUST be unguessable per request. */
export function buildAuthorizeUrl(state: string, callbackUrl: string): string {
  const u = new URL("https://github.com/login/oauth/authorize");
  u.searchParams.set("client_id", CLIENT_ID);
  u.searchParams.set("redirect_uri", callbackUrl);
  u.searchParams.set("scope", SCOPES);
  u.searchParams.set("state", state);
  u.searchParams.set("allow_signup", "true");
  return u.toString();
}

interface TokenExchangeResult {
  accessToken: string;
  scope: string;
  tokenType: string;
}

/** POST /login/oauth/access_token — exchange auth code for an access token. */
export async function exchangeCodeForToken(
  code: string, callbackUrl: string,
): Promise<TokenExchangeResult> {
  const res = await fetch("https://github.com/login/oauth/access_token", {
    method: "POST",
    headers: {
      "Accept": "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code,
      redirect_uri: callbackUrl,
    }),
  });
  const body = await res.json() as {
    access_token?: string; scope?: string; token_type?: string;
    error?: string; error_description?: string;
  };
  if (!res.ok || !body.access_token) {
    throw new Error(body.error_description || body.error || "GitHub token exchange failed");
  }
  return {
    accessToken: body.access_token,
    scope: body.scope ?? "",
    tokenType: body.token_type ?? "bearer",
  };
}

/** GET /user — fetch the authenticated user's login + name. */
export async function getAuthenticatedUser(token: string) {
  const res = await fetch("https://api.github.com/user", {
    headers: {
      "Authorization": `Bearer ${token}`,
      "Accept": "application/vnd.github+json",
      "X-GitHub-Api-Version": "2022-11-28",
    },
  });
  if (!res.ok) {
    throw new Error(`GitHub /user failed: ${res.status} ${res.statusText}`);
  }
  return res.json() as Promise<{ login: string; name: string | null; avatar_url: string }>;
}

interface GithubRepo {
  id: number;
  name: string;
  full_name: string;
  private: boolean;
  description: string | null;
  default_branch: string;
  html_url: string;
  pushed_at: string | null;
  language: string | null;
  fork: boolean;
}

/**
 * GET /user/repos — list repos the authenticated user can access.
 * Returns at most `perPage` repos; we cap at 100 (GitHub max) and don't
 * paginate further. The picker UI does client-side filter on top.
 */
export async function listUserRepos(token: string, perPage = 100): Promise<GithubRepo[]> {
  const u = new URL("https://api.github.com/user/repos");
  u.searchParams.set("per_page", String(perPage));
  u.searchParams.set("sort", "pushed");
  u.searchParams.set("affiliation", "owner,collaborator,organization_member");
  const res = await fetch(u.toString(), {
    headers: {
      "Authorization": `Bearer ${token}`,
      "Accept": "application/vnd.github+json",
      "X-GitHub-Api-Version": "2022-11-28",
    },
  });
  if (!res.ok) {
    throw new Error(`GitHub /user/repos failed: ${res.status} ${res.statusText}`);
  }
  return res.json() as Promise<GithubRepo[]>;
}

// ─────────────────────────────────────────────────────────────────────────────
// fs_users persistence
// ─────────────────────────────────────────────────────────────────────────────

export interface StoredGithubIntegration {
  login: string;
  accessToken: string; // encrypted
  scope: string;
  connectedAt: string;
}

/** Write the integration to fs_users.data.integrations.github. */
export async function persistGithubIntegration(
  email: string, login: string, plainToken: string, scope: string,
): Promise<void> {
  const blob: StoredGithubIntegration = {
    login,
    accessToken: encryptSecret(plainToken),
    scope,
    connectedAt: new Date().toISOString(),
  };
  await query(
    `UPDATE fs_users
        SET data = jsonb_set(
              jsonb_set(data, '{integrations}', COALESCE(data->'integrations','{}'::jsonb), true),
              '{integrations,github}', $2::jsonb, true
            ),
            updated_at = NOW()
      WHERE data->>'email' = $1`,
    [email, JSON.stringify(blob)],
  );
}

/** Read + decrypt the GitHub access token for a user, if any. */
export async function loadGithubIntegration(
  email: string,
): Promise<{ login: string; token: string; scope: string } | null> {
  const rows = await query<{ blob: StoredGithubIntegration | null }>(
    `SELECT data->'integrations'->'github' AS blob
       FROM fs_users WHERE data->>'email' = $1 LIMIT 1`,
    [email],
  );
  const blob = rows[0]?.blob;
  if (!blob || !blob.accessToken) return null;
  try {
    return { login: blob.login, token: decryptSecret(blob.accessToken), scope: blob.scope };
  } catch {
    return null;
  }
}

/** Drop the stored integration. */
export async function disconnectGithubIntegration(email: string): Promise<void> {
  await query(
    `UPDATE fs_users
        SET data = data #- '{integrations,github}',
            updated_at = NOW()
      WHERE data->>'email' = $1`,
    [email],
  );
}
