import { inject, injectable } from 'inversify';
import { JSDOM } from 'jsdom';
import { RequestInit, Response } from 'node-fetch';
import { Errors, RenaiError } from '../../core/error';
import { IWebCrawler } from '../web-crawler/i-web-crawler';
import { INhentaiApi } from './i-nhentai-api';

const domain = 'nhentai.net';
const url = `https://${domain}/`;

const paths = {
  books: 'g/',
  login: 'login/',
  favorites: 'favorites/',
};

const usernameInput = 'username_or_email';
const passwordInput = 'password';

interface ILoginMeta {
  [key: string]: string;
}

interface ILoginAuth {
  [usernameInput]: string;
  [passwordInput]: string;
}

interface ILoginParams extends ILoginMeta, ILoginAuth {}

@injectable()
export class NhentaiApi implements INhentaiApi {
  private webCrawler: IWebCrawler;

  public constructor(@inject(Symbol.for('web-crawler')) webCrawler: IWebCrawler) {
    this.webCrawler = webCrawler;
  }

  public isLoggedIn(): Promise<boolean> {
    return this.webCrawler
      .fetch(`${url}${paths.favorites}`, { redirect: 'manual' })
      .then((res: Response) => res.status === HttpCode.OK);
  }

  public login(name: string, password: string): Promise<void> {
    return this.getLoginMeta()
      .then((meta: ILoginMeta) => {
        const loginParams: ILoginParams = {
          ...meta,
          ...{
            [usernameInput]: name,
            [passwordInput]: password,
          },
        };

        return this.postNHentai(paths.login, {
          body: encodeURI(
            Object.keys(loginParams)
              .map((key: keyof ILoginParams) => `${key}=${loginParams[key]}`)
              .join('&')
          ),
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
          redirect: 'manual',
        });
      })
      .then(() => {})
      .catch(() => Promise.reject(new RenaiError(Errors.ELOGINFAIL)));
  }

  private getNHentai(path: string): Promise<Document> {
    return this.webCrawler
      .fetch(`${url}${path}`)
      .then((res: Response) => res.text())
      .then((text: string) => {
        const { document } = new JSDOM(text).window;
        return document;
      });
  }

  private postNHentai(path: string, requestInit: RequestInit = {}): Promise<Response> {
    const postUrl = `${url}${path}`;
    return this.webCrawler.fetch(postUrl, {
      ...requestInit,
      ...{
        headers: {
          ...requestInit.headers,
          ...{
            Host: domain,
            Referer: postUrl,
          },
        },
      },
      method: 'post',
    });
  }

  private getLoginMeta(): Promise<ILoginMeta> {
    return this.getNHentai(paths.login).then((document: Document) => {
      // eslint-disable-next-line @typescript-eslint/prefer-for-of
      for (let i = 0; i < document.forms.length; i++) {
        const form: HTMLFormElement = document.forms[i];
        const valueStore: ILoginMeta = {};
        let isLoginForm = false;
        // eslint-disable-next-line @typescript-eslint/prefer-for-of
        for (let j = 0; j < form.elements.length; j++) {
          const input = form.elements[j];
          const name = input.getAttribute('name');

          if (name === usernameInput || name === passwordInput) {
            isLoginForm = true;
          } else if (name) {
            valueStore[name] = input.getAttribute('value');
          }
        }
        if (isLoginForm) {
          return valueStore;
        }
      }
      return Promise.reject(new RenaiError(Errors.ENOLOGIN));
    });
  }
}
