import { useSessionStore } from "@/stores/session-store";
import type { z } from "zod";
import {
  DepotObjectSchema,
  HumanSchema,
  ListNetworksResponseSchema,
  NetworkSchema,
  PrepareUploadResponseSchema,
  SignInResponseSchema,
} from "./types";
import type {
  AddMembersRequest,
  CreateNetworkRequest,
  PrepareUploadRequest,
  RequestCodeRequest,
  SignInRequest,
} from "./types";

export class ApiError extends Error {
  constructor(
    public status: number,
    message: string,
  ) {
    super(message);
    this.name = "ApiError";
  }
}

interface ApiClientConfig {
  baseUrl: string;
  getToken: () => string | null;
  onUnauthorized: () => void;
}

class ApiClient {
  private config: ApiClientConfig;

  constructor(config: ApiClientConfig) {
    this.config = config;
  }

  private async fetch(
    method: string,
    path: string,
    body?: unknown,
  ): Promise<Response> {
    const headers: Record<string, string> = {};

    if (body) {
      headers["Content-Type"] = "application/json";
    }

    const token = this.config.getToken();
    if (token) {
      headers["Authorization"] = `Bearer ${token}`;
    }

    const response = await fetch(`${this.config.baseUrl}${path}`, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });

    if (response.status === 401) {
      this.config.onUnauthorized();
      throw new ApiError(401, "Unauthorized");
    }

    if (!response.ok) {
      const text = await response.text().catch(() => "Unknown error");
      throw new ApiError(response.status, text);
    }

    return response;
  }

  private async request<T>(
    schema: z.ZodType<T>,
    method: string,
    path: string,
    body?: unknown,
  ): Promise<T> {
    const response = await this.fetch(method, path, body);
    const json = await response.json();
    return schema.parse(json);
  }

  private async requestVoid(
    method: string,
    path: string,
    body?: unknown,
  ): Promise<void> {
    await this.fetch(method, path, body);
  }

  // --- Auth ---

  async requestCode(data: RequestCodeRequest): Promise<void> {
    await this.requestVoid("POST", "/auth/request-code", data);
  }

  async signIn(data: SignInRequest) {
    return this.request(SignInResponseSchema, "POST", "/auth/sign-in", data);
  }

  async me() {
    return this.request(HumanSchema, "GET", "/auth/me");
  }

  async signOut(): Promise<void> {
    await this.requestVoid("POST", "/auth/sign-out");
  }

  // TODO: security: require passing in the particle id once api deprecates this
  async getParticleDownloadUrl(objectId: string): Promise<string> {
    const response = await this.fetch(
      "GET",
      `/particles/${objectId}/download`,
    );
    const data = await response.json();
    return data.url;
  }

  // --- Depot ---

  async prepareUpload(data: PrepareUploadRequest) {
    return this.request(
      PrepareUploadResponseSchema,
      "POST",
      "/depot/upload",
      data,
    );
  }

  async confirmUpload(objectId: string) {
    return this.request(
      DepotObjectSchema,
      "POST",
      `/depot/objects/${objectId}/confirm`,
    );
  }


  // --- Networks ---

  async listNetworks() {
    return this.request(
      ListNetworksResponseSchema,
      "GET",
      "/networks",
    );
  }

  async createNetwork(data: CreateNetworkRequest) {
    return this.request(NetworkSchema, "POST", "/networks", data);
  }

  async getNetwork(id: string) {
    return this.request(NetworkSchema, "GET", `/networks/${id}`);
  }

  async addMembers(networkId: string, data: AddMembersRequest): Promise<void> {
    await this.requestVoid(
      "POST",
      `/networks/${networkId}/members`,
      data,
    );
  }

  async removeMember(networkId: string, email: string): Promise<void> {
    await this.requestVoid(
      "DELETE",
      `/networks/${networkId}/members/${email}`,
    );
  }
}

export const apiClient = new ApiClient({
  baseUrl: "https://orion.dev.flowy.live",
  getToken: () => useSessionStore.getState().token,
  onUnauthorized: () => useSessionStore.getState().clearToken(),
});
