/*
 * 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 {DaiaAvailabilityResponse, DaiaHolding, DaiaService} from './protocol/response';
import {StorageProvider} from '../storage/storage.provider';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {ConfigProvider} from '../config/config.provider';
import {SCFeatureConfiguration} from '@openstapps/core';
import {NGXLogger} from 'ngx-logger';
import {TranslateService} from '@ngx-translate/core';

/**
 *  Generated class for the DataProvider provider.
 *
 * See https://angular.io/guide/dependency-injection for more info on providers
 * and Angular DI.
 */
@Injectable({
  providedIn: 'root',
})
export class DaiaDataProvider {
  /**
   * TODO
   */
  storageProvider: StorageProvider;

  httpClient: HttpClient;

  daiaServiceUrl?: string;

  hebisProxyUrl?: string;

  clientHeaders = new HttpHeaders();

  /**
   * TODO
   * @param storageProvider TODO
   * @param httpClient TODO
   * @param configProvider TODO
   * @param logger TODO
   * @param translateService TODO
   */
  constructor(
    storageProvider: StorageProvider,
    httpClient: HttpClient,
    private configProvider: ConfigProvider,
    private readonly logger: NGXLogger,
    private translateService: TranslateService,
  ) {
    this.storageProvider = storageProvider;
    this.httpClient = httpClient;
    this.clientHeaders = this.clientHeaders.set('Content-Type', 'application/json');
  }

  async getAvailability(id: string): Promise<DaiaHolding[] | undefined> {
    if (this.daiaServiceUrl === undefined) {
      try {
        const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
        if (features.extern?.daia?.url) {
          this.daiaServiceUrl = features.extern?.daia?.url;
        } else {
          this.logger.error('Daia service url undefined');
          return undefined;
        }
        if (features.extern?.hebisProxy?.url) {
          this.hebisProxyUrl = features.extern?.hebisProxy?.url;
        } else {
          this.logger.error('HeBIS proxy url undefined');
          return undefined;
        }
      } catch (error) {
        this.logger.error(error);
        return undefined;
      }
    }

    return new Promise(resolve =>
      this.httpClient
        .get<DaiaAvailabilityResponse>(this.daiaServiceUrl as string, {
          params: {id, lang: this.translateService.currentLang},
        })
        .subscribe(
          (response: DaiaAvailabilityResponse) => {
            const holdings: DaiaHolding[] = [];
            if (response && Array.isArray(response.document)) {
              response.document.map(document => {
                Array.isArray(document.item) &&
                  document.item.map(element => {
                    try {
                      const {
                        department: {id: departmentId, content: departmentLabel, href: departmentLink} = {
                          id: 'noDep',
                          content: '',
                          href: '',
                        },
                        label,
                        about,
                        available,
                        storage,
                        chronology,
                        unavailable,
                      } = element;
                      const holdingStatus = this.holdingHasStatus(available || [])
                        ? this.getHoldingStatus(available || [], unavailable || [])
                        : undefined;

                      const dueDate =
                        holdingStatus === 'checked_out'
                          ? (unavailable.find(item => item.service === 'loan') as DaiaService).expected
                          : undefined;

                      holdings.push({
                        id: element.id,
                        department: {
                          id: departmentId,
                          content: departmentLabel,
                          href: departmentLink,
                        },
                        signature: label,
                        status: holdingStatus,
                        dueDate: dueDate,
                        online: Array.isArray(available) && available.some(item => item.service === 'remote'),
                        available:
                          (Array.isArray(available) &&
                            available.find(item =>
                              ['openaccess', 'loan', 'presentation'].includes(item.service),
                            )) ||
                          undefined,
                        unavailable:
                          (Array.isArray(unavailable) && unavailable.find(item => item.service === 'loan')) ||
                          undefined,
                        storage,
                        about,
                        holdings: chronology?.about,
                        open:
                          (Array.isArray(available) &&
                            available.some(item => item.service === 'openaccess')) ||
                          undefined,
                      });
                    } catch {
                      // No element available
                    }
                  });
              });
            }
            resolve(holdings);
          },
          error => {
            // handle "availability info not found" separately from the problems with getting the info
            if (error.status === 404) resolve([]);
            // eslint-disable-next-line unicorn/no-useless-undefined
            resolve(undefined);
          },
        ),
    );
  }

  getHoldingLink(holding: DaiaHolding, open = false) {
    if (this.hebisProxyUrl === undefined) {
      this.logger.error('HeBIS proxy url undefined');

      return;
    }
    const resourceLink = holding.available?.href;
    return open ? resourceLink : `${this.hebisProxyUrl}${encodeURIComponent(resourceLink as string)}`;
  }

  holdingHasStatus(available: DaiaService[]): boolean {
    return !available.some(item => item.service === 'remote');
  }

  getHoldingStatus(available: DaiaService[], unavailable: DaiaService[]): DaiaHolding['status'] {
    const loan: {available: number; unavailable: number} = {
      available: available.findIndex(item => item.service === 'loan'),
      unavailable: unavailable.findIndex(item => item.service === 'loan'),
    };
    const presentation: {available: number; unavailable: number} = {
      available: available.findIndex(item => item.service === 'presentation'),
      unavailable: unavailable.findIndex(item => item.service === 'presentation'),
    };

    if (loan.unavailable !== -1 && unavailable[loan.unavailable].expected !== undefined) {
      return 'checked_out';
    }

    if (
      loan.unavailable !== -1 &&
      unavailable[loan.unavailable].limitations?.some(limitation =>
        ['OnBuy', 'JustReturned'].includes(limitation.id),
      )
    )
      return 'not_yet_available';

    if (
      loan.unavailable !== -1 &&
      unavailable[loan.unavailable].limitations?.some(limitation =>
        ['CopyIsMissing', 'Canceled'].includes(limitation.id),
      )
    )
      return 'not_available';

    if (
      (loan.unavailable !== -1 &&
        unavailable[loan.unavailable].limitations?.some(limitation =>
          ['OnlyInHouse'].includes(limitation.id),
        )) ||
      (loan.available !== -1 &&
        available[loan.available].limitations?.some(limitation =>
          ['ExternalLoan'].includes(limitation.id),
        )) ||
      (loan.available === -1 && presentation.available !== -1 && presentation.unavailable === -1)
    )
      return 'library_only';

    if (loan.available !== -1) return 'available';

    return 'unknown';
  }
}
