import { POST_LIMIT } from '../constanst';
import { PostAuth, PostListAuth, PostListNonAuth, PostUpdate } from '../types/Post';
import { User, UserUpdate } from '../types/User';

const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';

let instance: ApiImpl;

class ApiImpl {
  private token?: string;
  private refreshToken?: string;
  private self?: User;

  constructor() {
    if (instance) {
      throw new Error('New instance cannot be created!!');
    }

    //eslint-disable-next-line @typescript-eslint/no-this-alias
    instance = this;
  }

  public hasAuth = () => this.token !== undefined;
  //FIXME: TESTING
  public isAdmin = () => this.hasAuth() && this.self?.isAdmin;
  public getAuthenticatedUser = () => this.self;
  public getCurrentSession = () => [this.token, this.refreshToken];

  public logIn = async (email: string, password: string): Promise<void> => {
    const { user, token } = await (await this.post('login', { email, password })).json();
    this.self = user;
    this.token = token;
  };

  public logOut = async (): Promise<boolean> => {
    try {
      return await (await this.postAuth('logout')).json();
    } catch {
      return false;
    } finally {
      this.self = undefined;
      this.token = undefined;
    }
  };

  public posts = async (page?: number): Promise<PostListNonAuth | PostListAuth> => {
    const url = `posts?p=${page ?? 0}&l=${POST_LIMIT}`;

    if (this.token) return await (await this.getAuth(url)).json();

    return await (await this.get(url)).json();
  };

  public deletePost = async (id: number): Promise<PostAuth> => {
    return await (await this.delete(`posts/${id}`)).json();
  };

  public user = async (id?: number): Promise<User> => {
    return await (await this.getAuth(`users/${id ?? this.self?.id}`)).json();
  };

  public updateUser = async (data: UserUpdate, id?: number): Promise<User> => {
    return await (await this.patch(`users/${id ?? 'self'}`, data as Record<string, unknown>)).json();
  };

  public newPost = async (data: PostUpdate): Promise<PostAuth> => {
    return await (await this.postAuth(`posts`, data as Record<string, unknown>)).json();
  };

  public updatePost = async (data: PostUpdate, id: number): Promise<PostAuth> => {
    return await (await this.patch(`posts/${id}`, data as Record<string, unknown>)).json();
  };

  /* Internal */

  private post = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
    const response = await fetch(`${BASE}${endpoint}`, {
      mode: 'cors',
      method: 'post',
      headers,
      body: JSON.stringify(body),
    });
    if (response.ok) return response;
    throw await response.json();
  };

  private postAuth = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
    const response = await fetch(`${BASE}${endpoint}`, {
      mode: 'cors',
      method: 'post',
      headers: { token: this.token ?? '', ...headers },
      body: JSON.stringify(body),
    });
    if (response.ok) return response;
    throw await response.json();
  };

  private get = async (endpoint: string, headers?: HeadersInit) => {
    const response = await fetch(`${BASE}${endpoint}`, {
      mode: 'cors',
      method: 'get',
      headers,
    });
    if (response.ok) return response;
    throw await response.json();
  };

  private getAuth = async (endpoint: string, headers?: HeadersInit) => {
    const response = await fetch(`${BASE}${endpoint}`, {
      mode: 'cors',
      method: 'get',
      headers: { token: this.token ?? '', ...headers },
    });
    if (response.ok) return response;
    throw await response.json();
  };

  private delete = async (endpoint: string, headers?: HeadersInit) => {
    const response = await fetch(`${BASE}${endpoint}`, {
      mode: 'cors',
      method: 'delete',
      headers: { token: this.token ?? '', ...headers },
    });
    if (response.ok) return response;
    throw await response.json();
  };

  private patch = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
    const response = await fetch(`${BASE}${endpoint}`, {
      mode: 'cors',
      method: 'patch',
      headers: { token: this.token ?? '', ...headers },
      body: JSON.stringify(body),
    });
    if (response.ok) return response;
    throw await response.json();
  };
}

const Api = new ApiImpl();
export default Api;
