// Clients des services git distants (Gitea / GitLab / GitHub) : P12. Uniquement `fetch` global
// (Node ≥ 22), AUCUNE dépendance (pas d'octokit/gitbeaker). Chaque client expose verify() (test de
// connectivité/auth) et listRepos() (paginé). Erreurs typées : AUTH_FAILED / RATE_LIMITED / UNREACHABLE.
import type { GitAuthType, GitService, RemoteRepoSummary } from '@arboretum/shared';

const REQUEST_TIMEOUT_MS = 12_000;
const PER_PAGE = 30;

export interface GitAuth {
  authType: GitAuthType;
  username: string | null;
  secret: string;
}

export class GitServiceError extends Error {
  constructor(
    public readonly errorCode: string,
    message?: string,
  ) {
    super(message ?? errorCode);
    this.name = 'GitServiceError';
  }
}

export interface GitClient {
  verify(auth: GitAuth): Promise<{ login: string }>;
  listRepos(auth: GitAuth, page: number, search?: string): Promise<{ repos: RemoteRepoSummary[]; nextPage: number | null }>;
}

/** En-têtes d'auth selon le type. app_password → Basic (username:secret) ; pat → en-tête propre au service. */
function authHeaders(service: GitService, auth: GitAuth): Record<string, string> {
  if (auth.authType === 'app_password') {
    const basic = Buffer.from(`${auth.username ?? ''}:${auth.secret}`).toString('base64');
    return { Authorization: `Basic ${basic}` };
  }
  // pat
  if (service === 'gitlab') return { 'PRIVATE-TOKEN': auth.secret };
  if (service === 'gitea') return { Authorization: `token ${auth.secret}` };
  return { Authorization: `Bearer ${auth.secret}` }; // github
}

/** Refuse une base self-hosted non http(s) (anti-SSRF schéma) ; renvoie l'origine normalisée sans `/` final. */
function normalizeBase(baseUrl: string): string {
  let u: URL;
  try {
    u = new URL(baseUrl);
  } catch {
    throw new GitServiceError('BAD_BASE_URL', 'base_url must be a valid http(s) URL');
  }
  if (u.protocol !== 'http:' && u.protocol !== 'https:') throw new GitServiceError('BAD_BASE_URL', 'base_url must be http(s)');
  return `${u.origin}${u.pathname}`.replace(/\/+$/, '');
}

async function fetchJson(url: string, headers: Record<string, string>): Promise<{ json: unknown; headers: Headers }> {
  let res: Response;
  try {
    res = await fetch(url, { headers: { Accept: 'application/json', ...headers }, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
  } catch {
    throw new GitServiceError('UNREACHABLE', 'Could not reach the git service');
  }
  if (res.status === 429) throw new GitServiceError('RATE_LIMITED', 'Rate limited by the git service');
  if (res.status === 401 || res.status === 403) {
    // 403 + quota épuisé = rate limit (GitHub) ; sinon échec d'auth.
    if (res.headers.get('x-ratelimit-remaining') === '0') throw new GitServiceError('RATE_LIMITED', 'Rate limited by the git service');
    throw new GitServiceError('AUTH_FAILED', 'Authentication failed');
  }
  if (!res.ok) throw new GitServiceError(`HTTP_${res.status}`, `Unexpected response ${res.status}`);
  return { json: await res.json().catch(() => null), headers: res.headers };
}

function githubBase(baseUrl: string | null): string {
  return baseUrl ? normalizeBase(baseUrl) : 'https://api.github.com';
}
function gitlabBase(baseUrl: string | null): string {
  return `${baseUrl ? normalizeBase(baseUrl) : 'https://gitlab.com'}/api/v4`;
}
function giteaBase(baseUrl: string | null): string {
  if (!baseUrl) throw new GitServiceError('BAD_BASE_URL', 'Gitea requires a base_url (self-hosted instance)');
  return `${normalizeBase(baseUrl)}/api/v1`;
}

const githubClient = (baseUrl: string | null): GitClient => {
  const base = githubBase(baseUrl);
  return {
    async verify(auth) {
      const { json } = await fetchJson(`${base}/user`, authHeaders('github', auth));
      return { login: String((json as { login?: string })?.login ?? '') };
    },
    async listRepos(auth, page) {
      const { json } = await fetchJson(`${base}/user/repos?per_page=${PER_PAGE}&page=${page}&sort=updated`, authHeaders('github', auth));
      const arr = Array.isArray(json) ? (json as Array<Record<string, unknown>>) : [];
      const repos = arr.map((r): RemoteRepoSummary => ({
        fullName: String(r.full_name ?? ''),
        cloneUrl: String(r.clone_url ?? ''),
        private: Boolean(r.private),
        description: (r.description as string | null) ?? null,
        defaultBranch: (r.default_branch as string | null) ?? null,
      }));
      return { repos, nextPage: arr.length === PER_PAGE ? page + 1 : null };
    },
  };
};

const gitlabClient = (baseUrl: string | null): GitClient => {
  const base = gitlabBase(baseUrl);
  return {
    async verify(auth) {
      const { json } = await fetchJson(`${base}/user`, authHeaders('gitlab', auth));
      return { login: String((json as { username?: string })?.username ?? '') };
    },
    async listRepos(auth, page) {
      const { json, headers } = await fetchJson(
        `${base}/projects?membership=true&per_page=${PER_PAGE}&page=${page}&order_by=last_activity_at`,
        authHeaders('gitlab', auth),
      );
      const arr = Array.isArray(json) ? (json as Array<Record<string, unknown>>) : [];
      const repos = arr.map((r): RemoteRepoSummary => ({
        fullName: String(r.path_with_namespace ?? ''),
        cloneUrl: String(r.http_url_to_repo ?? ''),
        private: r.visibility !== 'public',
        description: (r.description as string | null) ?? null,
        defaultBranch: (r.default_branch as string | null) ?? null,
      }));
      const next = headers.get('x-next-page');
      return { repos, nextPage: next ? Number(next) : null };
    },
  };
};

const giteaClient = (baseUrl: string | null): GitClient => {
  const base = giteaBase(baseUrl);
  return {
    async verify(auth) {
      const { json } = await fetchJson(`${base}/user`, authHeaders('gitea', auth));
      return { login: String((json as { login?: string })?.login ?? '') };
    },
    async listRepos(auth, page) {
      const { json } = await fetchJson(`${base}/user/repos?page=${page}&limit=${PER_PAGE}`, authHeaders('gitea', auth));
      const arr = Array.isArray(json) ? (json as Array<Record<string, unknown>>) : [];
      const repos = arr.map((r): RemoteRepoSummary => ({
        fullName: String(r.full_name ?? ''),
        cloneUrl: String(r.clone_url ?? ''),
        private: Boolean(r.private),
        description: (r.description as string | null) ?? null,
        defaultBranch: (r.default_branch as string | null) ?? null,
      }));
      return { repos, nextPage: arr.length === PER_PAGE ? page + 1 : null };
    },
  };
};

export function getGitClient(service: GitService, baseUrl: string | null): GitClient {
  if (service === 'github') return githubClient(baseUrl);
  if (service === 'gitlab') return gitlabClient(baseUrl);
  return giteaClient(baseUrl);
}
