import _ from 'lodash';
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
import throat from 'throat';
import { NoteChecker } from './note-checker';

import request from 'request-promise'; //tslint:disable-line:match-default-export-name

/* tslint:disable:no-unsafe-any */

export interface SiteSessionInterface {
  start(): Promise<void>;
  stop(): Promise<void>;
}

export interface SiteSessionInterfaceCollection extends Record<string, SiteSessionInterface> {
  notes: NoteChecker;
}


export class SiteSession {
  private readonly sessionThroat = throat(1);

  readonly interfaces: SiteSessionInterfaceCollection = {
    notes: new NoteChecker(this)
  };

  private state: 'active' | 'inactive' = 'inactive';
  private account = '';
  private password = '';

  private request: request.RequestPromiseAPI = request.defaults({ jar: request.jar() });

  private csrf = '';


  setCredentials(account: string, password: string): void {
    this.account = account;
    this.password = password;
  }


  async start(): Promise<void> {
    try {
      await this.stop();
      await this.init();
      await this.login();

      this.state = 'active';

      await Promise.all(
        _.map(this.interfaces, (i) => i.start())
      );
    } catch(err) {
      this.state = 'inactive';
      log.error('sitesession.start.error', err);
    }
  }


  async stop(): Promise<void> {
    try {
      await Promise.all(
        _.map(this.interfaces, (i) => i.stop())
      );
    } catch(err) {
      log.error('sitesession.stop.error', err);
    }

    this.csrf = '';
    this.state = 'inactive';
  }


  private async init(): Promise<void> {
    log.debug('sitesession.init');

    this.request = request.defaults({ jar: request.jar() });
    this.csrf = '';

    const res = await this.get('/');

    if (res.statusCode !== 200) {
      throw new Error(`SiteSession.init: Invalid status code: ${res.status}`);
    }

    const input = res.body.match(/<input.*?csrf_token.*?>/);

    if ((!input) || (input.length < 1)) {
      throw new Error('SiteSession.init: Missing csrf token');
    }

    const csrf = input[0].match(/value="([a-zA-Z0-9]+)"/);

    if ((!csrf) || (csrf.length < 2)) {
      throw new Error('SiteSession.init: Missing csrf token value');
    }

    this.csrf = csrf[1];
  }


  private async login(): Promise<void> {
    log.debug('sitesession.login');

    if ((this.password === '') || (this.account === '')) {
      throw new Error('User credentials not set');
    }

    const res = await this.post(
      '/action/script_login.php',
      {
        username: this.account,
        password: this.password,
        csrf_token: this.csrf
      },
      false,
      {
        followRedirect: false,
        simple: false
      }
    );

    if (res.statusCode !== 302) {
      throw new Error('Invalid status code');
    }

    // console.log('RES RES RES', res);

    log.debug('sitesession.login.success');
  }


  // tslint:disable-next-line:prefer-function-over-method
  private async ensureLogin(): Promise<void> {
    if (this.state !== 'active') {
      throw new Error('Site session not active');
    }
  }


  private async prepareRequest(
    method: string,
    uri: string,
    mustBeLoggedIn: boolean,
    config: Partial<request.Options>
  ): Promise<request.OptionsWithUri> {
    if (mustBeLoggedIn) {
      await this.ensureLogin();
    }

    return _.merge(
      {
        method,
        uri: `https://www.f-list.net${uri}`,
        resolveWithFullResponse: true
      },
      config
    );
  }


  async get(uri: string, mustBeLoggedIn: boolean = false, config: Partial<request.Options> = {}): Promise<request.RequestPromise> {
    return this.sessionThroat(
      async() => {
          const finalConfig = await this.prepareRequest('get', uri, mustBeLoggedIn, config);

          return this.request(finalConfig);
      }
    );
  }


  async post(
    uri: string,
    data: Record<string, any>,
    mustBeLoggedIn: boolean = false,
    config: Partial<request.Options> = {}
  ): Promise<request.RequestPromise> {
    return this.sessionThroat(
      async() => {
        const finalConfig = await this.prepareRequest('post', uri, mustBeLoggedIn, _.merge({ form: data }, config));

        return this.request(finalConfig);
      }
    );
  }


  async onConnectionClosed(): Promise<void> {
    await this.stop();
  }


  async onConnectionEstablished(): Promise<void> {
    await this.start();
  }
}