/*
 * Copyright (C) 2023 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

import {Injectable} from '@angular/core';
import {JQueryRequestor, Requestor} from '@openid/appauth';
import {
  SCAuthorizationProviderType,
  SCFeatureConfiguration,
  SCFeatureConfigurationExtern,
} from '@openstapps/core';
import {
  DocumentAction,
  PAIADocument,
  PAIADocumentStatus,
  PAIAFees,
  PAIAItems,
  PAIAPatron,
  PAIAPatronStatus,
} from '../types';
import {HebisDataProvider} from '../../hebis/hebis-data.provider';
import {PAIATokenResponse} from '../../auth/paia/paia-token-response';
import {AuthHelperService} from '../../auth/auth-helper.service';
import {ConfigProvider} from '../../config/config.provider';
import {TranslateService} from '@ngx-translate/core';
import {AlertController, ToastController} from '@ionic/angular/standalone';
import {HebisSearchResponse} from '../../hebis/protocol/response';

@Injectable({
  providedIn: 'root',
})
export class LibraryAccountService {
  /**
   * Base url of the external service
   */
  baseUrl: string;

  /**
   * Authorization provider type
   */
  authType: SCAuthorizationProviderType;

  /**
   * Account (Patron) status
   */
  private status?: PAIAPatronStatus;

  constructor(
    protected requestor: Requestor = new JQueryRequestor(),
    private readonly hebisDataProvider: HebisDataProvider,
    private readonly authHelper: AuthHelperService,
    readonly configProvider: ConfigProvider,
    private readonly translateService: TranslateService,
    private readonly alertController: AlertController,
    private readonly toastController: ToastController,
  ) {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    const config: SCFeatureConfigurationExtern = (
      configProvider.getValue('features') as SCFeatureConfiguration
    ).extern!.paia;
    this.baseUrl = config.url;
    this.authType = config.authProvider as SCAuthorizationProviderType;
  }

  async getPatron() {
    const patronId = ((await this.getValidToken()) as PAIATokenResponse).patron;
    const patron = {
      ...(await this.performRequest<PAIAPatron>(`${this.baseUrl}/{patron}`)),
      id: patronId,
    } as PAIAPatron;
    // Refresh the status
    this.status = Number(patron.status);
    return patron;
  }

  async getPatronStatus() {
    return this.status ?? (this.status = Number((await this.getPatron()).status) as PAIAPatronStatus);
  }

  async isActivePatron() {
    return (await this.getPatronStatus()) === PAIAPatronStatus.Active;
  }

  async getItems() {
    return this.performRequest<PAIAItems>(`${this.baseUrl}/{patron}/items`);
  }

  async getFees() {
    return this.performRequest<PAIAFees>(`${this.baseUrl}/{patron}/fees`);
  }

  async getValidToken() {
    return this.authHelper.getProvider(this.authType).getValidToken();
  }

  private async performRequest<T>(
    urlTemplate: string,
    method = 'GET',
    data?: JQuery.PlainObject,
  ): Promise<T | undefined> {
    const token = await this.getValidToken();
    const url = urlTemplate.replace('{patron}', (token as PAIATokenResponse).patron);
    const settings: JQueryAjaxSettings = {
      url: url,
      dataType: 'json',
      method: method,
      headers: {
        'Authorization': `Bearer: ${token.accessToken}`,
        'Content-Type': 'application/json',
      },
    };

    if (method === 'POST') settings.data = data;

    let result: T;

    try {
      result = await this.requestor.xhr(settings);
      return result;
    } catch {
      void this.handleError();
    }

    return;
  }

  getRawId(input: string) {
    return input.split(':').pop();
  }

  async getFilteredItems(documentStatus: PAIADocumentStatus[]) {
    return (await this.getItems())?.doc.filter(document => {
      return documentStatus.includes(Number(document.status) as PAIADocumentStatus);
    });
  }

  async getDocumentFromHDS(edition: string) {
    if (edition === undefined) {
      return;
    }

    let response: HebisSearchResponse;

    try {
      response = await this.hebisDataProvider.hebisSearch(
        {
          query: this.getRawId(edition) as string,
          page: 0,
        },
        {addPrefix: true},
      );
      return response.data[0];
    } catch {
      await this.handleError();
    }

    return;
  }

  async cancelReservation(document: PAIADocument) {
    const result = await this.performRequest<void>(`${this.baseUrl}/{patron}/cancel`, 'POST', {
      doc: [document],
    });
    if (result) {
      void this.onSuccess('cancel');
    }
  }

  async renewLending(document: PAIADocument) {
    const result = await this.performRequest<void>(`${this.baseUrl}/{patron}/renew`, 'POST', {
      doc: [document],
    });
    if (result) {
      void this.onSuccess('renew');
    }
  }

  async handleDocumentAction(documentAction: DocumentAction): Promise<boolean> {
    return new Promise(async resolve => {
      const handleDocument = () => {
        switch (documentAction.action) {
          case 'cancel': {
            return this.cancelReservation(documentAction.doc);
            break;
          }
          case 'renew': {
            return this.renewLending(documentAction.doc);
          }
        }
      };
      const alert = await this.alertController.create({
        buttons: [
          {
            role: 'cancel',
            text: this.translateService.instant('abort'),
            handler: () => {
              resolve(false);
            },
          },
          {
            handler: async () => {
              await handleDocument();
              resolve(true);
            },
            text: this.translateService.instant('OK'),
          },
        ],
        header: this.translateService.instant(`library.account.actions.${documentAction.action}.header`),
        message: this.translateService.instant(`library.account.actions.${documentAction.action}.text`, {
          value:
            documentAction.doc.about ??
            this.translateService.instant(`library.account.actions.${documentAction.doc.about}.unknown_book`),
        }),
      });
      await alert.present();
    });
  }

  async handleError() {
    const alert = await this.alertController.create({
      header: this.translateService.instant('app.ui.ERROR'),
      message: this.translateService.instant('app.errors.SERVICE'),
      buttons: ['OK'],
    });

    await alert.present();
  }

  async onSuccess(action: DocumentAction['action']) {
    const toast = await this.toastController.create({
      message: this.translateService.instant(`library.account.actions.${action}.success`),
      duration: 2000,
      color: 'success',
    });
    await toast.present();
  }
}
