/**
 * Gitea API client for Vibn project provisioning.
 *
 * Used server-side only. Credentials come from env vars:
 *   GITEA_API_URL  — e.g. https://git.vibnai.com
 *   GITEA_API_TOKEN — admin token
 *   GITEA_ADMIN_USER — default owner for repos (e.g. "mark")
 */

const GITEA_API_URL = process.env.GITEA_API_URL ?? 'https://git.vibnai.com';
const GITEA_API_TOKEN = process.env.GITEA_API_TOKEN ?? '';
const GITEA_ADMIN_USER = process.env.GITEA_ADMIN_USER ?? 'mark';

export interface GiteaRepo {
  id: number;
  name: string;
  full_name: string;
  html_url: string;
  clone_url: string;
  ssh_url: string;
  private: boolean;
  default_branch: string;
}

export interface GiteaWebhook {
  id: number;
  type: string;
  active: boolean;
  config: {
    url: string;
    content_type: string;
    secret?: string;
  };
}

async function giteaFetch(path: string, options: RequestInit = {}) {
  const url = `${GITEA_API_URL}/api/v1${path}`;
  const res = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      Authorization: `token ${GITEA_API_TOKEN}`,
      ...(options.headers ?? {}),
    },
  });

  if (!res.ok) {
    const text = await res.text();
    throw new Error(`Gitea API error ${res.status} on ${path}: ${text}`);
  }

  if (res.status === 204) return null;
  return res.json();
}

/**
 * Create a new repo under the admin user (or a specified owner).
 */
export async function createRepo(
  name: string,
  opts: { description?: string; private?: boolean; owner?: string; auto_init?: boolean } = {}
): Promise<GiteaRepo> {
  const { description = '', private: isPrivate = true, owner = GITEA_ADMIN_USER, auto_init = true } = opts;

  return giteaFetch(`/user/repos`, {
    method: 'POST',
    body: JSON.stringify({
      name,
      description,
      private: isPrivate,
      auto_init,
      default_branch: 'main',
    }),
  });
}

/**
 * Get an existing repo.
 */
export async function getRepo(owner: string, repo: string): Promise<GiteaRepo | null> {
  try {
    return await giteaFetch(`/repos/${owner}/${repo}`);
  } catch {
    return null;
  }
}

/**
 * Delete a repo (used for project cleanup).
 */
export async function deleteRepo(owner: string, repo: string): Promise<void> {
  await giteaFetch(`/repos/${owner}/${repo}`, { method: 'DELETE' });
}

/**
 * Register a webhook on a repo that fires on push, PR, and issue events.
 *
 * @param owner      Repo owner (user or org)
 * @param repo       Repo name
 * @param webhookUrl Target URL — should include projectId as query param
 * @param secret     Shared secret for payload signature verification
 */
export async function createWebhook(
  owner: string,
  repo: string,
  webhookUrl: string,
  secret: string
): Promise<GiteaWebhook> {
  return giteaFetch(`/repos/${owner}/${repo}/hooks`, {
    method: 'POST',
    body: JSON.stringify({
      type: 'gitea',
      active: true,
      events: ['push', 'pull_request', 'issues', 'issue_comment'],
      config: {
        url: webhookUrl,
        content_type: 'json',
        secret,
      },
    }),
  });
}

/**
 * List webhooks on a repo.
 */
export async function listWebhooks(owner: string, repo: string): Promise<GiteaWebhook[]> {
  return giteaFetch(`/repos/${owner}/${repo}/hooks`);
}

/**
 * Delete a webhook.
 */
export async function deleteWebhook(owner: string, repo: string, hookId: number): Promise<void> {
  await giteaFetch(`/repos/${owner}/${repo}/hooks/${hookId}`, { method: 'DELETE' });
}

/**
 * Verify the X-Gitea-Signature-256 header on an incoming webhook payload.
 * Returns true if the signature matches.
 */
export async function verifyWebhookSignature(
  body: string,
  signature: string,
  secret: string
): Promise<boolean> {
  if (!signature?.startsWith('sha256=')) return false;

  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );

  const sigBytes = await crypto.subtle.sign('HMAC', key, encoder.encode(body));
  const expected = 'sha256=' + Array.from(new Uint8Array(sigBytes))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  return expected === signature;
}

/**
 * Push a single file to a repo via the Gitea contents API.
 * Creates the file if it doesn't exist; updates it if it does.
 */
export async function giteaPushFile(
  owner: string,
  repo: string,
  path: string,
  content: string,
  message: string,
  branch = 'main',
): Promise<void> {
  const encoded = Buffer.from(content).toString('base64');

  // Check if file already exists to get its SHA (required for updates)
  let sha: string | undefined;
  try {
    const existing = await giteaFetch(`/repos/${owner}/${repo}/contents/${path}?ref=${branch}`);
    sha = (existing as any)?.sha;
  } catch {
    // File doesn't exist — create it
  }

  await giteaFetch(`/repos/${owner}/${repo}/contents/${path}`, {
    method: sha ? 'PUT' : 'POST',
    body: JSON.stringify({ message, content: encoded, branch, ...(sha ? { sha } : {}) }),
  });
}

export const GITEA_ADMIN_USER_EXPORT = GITEA_ADMIN_USER;
