import { Base64 } from "js-base64";
import type { GitProvider } from "@tinacms/datalayer";

export interface GiteaGitProviderOptions {
  owner: string;
  repo: string;
  token: string;
  branch: string;
  baseUrl: string;
  commitMessage?: string;
  rootPath?: string;
}

export class GiteaGitProvider implements GitProvider {
  owner: string;
  repo: string;
  branch: string;
  baseUrl: string;
  commitMessage: string;
  rootPath?: string;
  private token: string;

  constructor(args: GiteaGitProviderOptions) {
    this.owner = args.owner;
    this.repo = args.repo;
    this.branch = args.branch;
    this.baseUrl = args.baseUrl.replace(/\/$/, "");
    this.commitMessage = args.commitMessage || "Edited with TinaCMS";
    this.rootPath = args.rootPath;
    this.token = args.token;
  }

  private getApiUrl(path: string): string {
    return `${this.baseUrl}/api/v1/repos/${this.owner}/${this.repo}/contents/${path}`;
  }

  private getHeaders(): Record<string, string> {
    return {
      Authorization: `token ${this.token}`,
      "Content-Type": "application/json",
      Accept: "application/json",
    };
  }

  private getKeyWithPath(key: string): string {
    return this.rootPath ? `${this.rootPath}/${key}` : key;
  }

  private async getFileSha(
    keyWithPath: string
  ): Promise<string | undefined> {
    try {
      const url = `${this.getApiUrl(keyWithPath)}?ref=${encodeURIComponent(this.branch)}`;
      const response = await fetch(url, {
        method: "GET",
        headers: this.getHeaders(),
      });
      if (!response.ok) {
        return undefined;
      }
      const data = await response.json();
      return data.sha;
    } catch {
      return undefined;
    }
  }

  async onPut(key: string, value: string): Promise<void> {
    const keyWithPath = this.getKeyWithPath(key);
    const sha = await this.getFileSha(keyWithPath);

    const body: Record<string, unknown> = {
      message: this.commitMessage,
      content: Base64.encode(value),
      branch: this.branch,
    };

    if (sha) {
      body.sha = sha;
    }

    const response = await fetch(this.getApiUrl(keyWithPath), {
      method: sha ? "PUT" : "POST",
      headers: this.getHeaders(),
      body: JSON.stringify(body),
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(
        `Failed to save ${keyWithPath}: ${response.status} ${errorText}`
      );
    }
  }

  async onDelete(key: string): Promise<void> {
    const keyWithPath = this.getKeyWithPath(key);
    const sha = await this.getFileSha(keyWithPath);

    if (!sha) {
      throw new Error(
        `Could not find file ${keyWithPath} in repo ${this.owner}/${this.repo}`
      );
    }

    const response = await fetch(this.getApiUrl(keyWithPath), {
      method: "DELETE",
      headers: this.getHeaders(),
      body: JSON.stringify({
        message: this.commitMessage,
        branch: this.branch,
        sha,
      }),
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(
        `Failed to delete ${keyWithPath}: ${response.status} ${errorText}`
      );
    }
  }
}
