/*
 * Copyright (C) 2022 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 {Client} from '@openstapps/api/lib/client';
import {SCAppConfiguration, SCIndexResponse} from '@openstapps/core';
import {NGXLogger} from 'ngx-logger';
import packageJson from '../../../../package.json';
import {environment} from '../../../environments/environment';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {StorageProvider} from '../storage/storage.provider';
import {
  ConfigFetchError,
  ConfigInitError,
  ConfigValueNotAvailable,
  SavedConfigNotAvailable,
  WrongConfigVersionInStorage,
} from './errors';

/**
 * Key to store config in storage module
 *
 * TODO: Issue #41 centralise storage keys
 */
export const STORAGE_KEY_CONFIG = 'stapps.config';

/**
 * Provides configuration
 */
@Injectable({
  providedIn: 'root',
})
export class ConfigProvider {
  /**
   * Api client
   */
  client: Client;

  /**
   * App configuration as IndexResponse
   */
  config: SCIndexResponse;

  /**
   * Version of the @openstapps/core package that app is using
   */
  scVersion = packageJson.dependencies['@openstapps/core'];

  /**
   * First session indicator (config not found in storage)
   */
  firstSession = true;

  /**
   * Constructor, initialise api client
   *
   * @param storageProvider StorageProvider to load persistent configuration
   * @param swHttpClient Api client
   * @param logger An angular logger
   */
  constructor(
    private readonly storageProvider: StorageProvider,
    swHttpClient: StAppsWebHttpClient,
    private readonly logger: NGXLogger,
  ) {
    this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version);
  }

  /**
   * Fetches configuration from backend
   */
  async fetch(): Promise<SCIndexResponse> {
    try {
      return await this.client.handshake(this.scVersion);
    } catch {
      throw new ConfigFetchError();
    }
  }

  /**
   * Returns the value of an app configuration
   *
   * @param attribute requested attribute from app configuration
   */
  public getValue(attribute: keyof SCAppConfiguration) {
    if (typeof this.config.app[attribute] !== 'undefined') {
      return this.config.app[attribute];
    }
    throw new ConfigValueNotAvailable(attribute);
  }

  /**
   * Returns a value of the configuration (not only app configuration)
   *
   * @param attribute requested attribute from the configuration
   */
  public getAnyValue(attribute: keyof SCIndexResponse) {
    if (typeof this.config[attribute] !== 'undefined') {
      return this.config[attribute];
    }
    throw new ConfigValueNotAvailable(attribute);
  }

  /**
   * Initialises the ConfigProvider
   *
   * @throws ConfigInitError if no configuration could be loaded.
   * @throws WrongConfigVersionInStorage if fetch failed and saved config has wrong SCVersion
   */
  async init(): Promise<void> {
    let loadError;
    let fetchError;
    // load saved configuration
    try {
      this.config = await this.loadLocal();
      this.firstSession = false;
      this.logger.log(`initialised configuration from storage`);
      if (this.config.backend.SCVersion !== this.scVersion) {
        loadError = new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion);
      }
    } catch (error) {
      loadError = error;
    }
    // fetch remote configuration from backend
    try {
      const fetchedConfig: SCIndexResponse = await this.fetch();
      await this.set(fetchedConfig);
      this.logger.log(`initialised configuration from remote`);
    } catch (error) {
      fetchError = error;
    }
    // check for occurred errors and throw them
    if (typeof loadError !== 'undefined' && typeof fetchError !== 'undefined') {
      throw new ConfigInitError();
    }
    if (typeof loadError !== 'undefined') {
      this.logger.warn(loadError);
    }
    if (typeof fetchError !== 'undefined') {
      this.logger.warn(fetchError);
    }
  }

  /**
   * Returns saved configuration from StorageModule
   *
   * @throws SavedConfigNotAvailable if no configuration could be loaded
   */
  async loadLocal(): Promise<SCIndexResponse> {
    // get local configuration
    if (await this.storageProvider.has(STORAGE_KEY_CONFIG)) {
      return this.storageProvider.get<SCIndexResponse>(STORAGE_KEY_CONFIG);
    }
    throw new SavedConfigNotAvailable();
  }

  /**
   * Saves the configuration from the provider
   *
   * @param config configuration to save
   */
  async save(config: SCIndexResponse): Promise<void> {
    await this.storageProvider.put(STORAGE_KEY_CONFIG, config);
  }

  /**
   * Sets the configuration in the module and writes it into app storage
   *
   * @param config SCIndexResponse to set
   */
  async set(config: SCIndexResponse): Promise<void> {
    this.config = config;
    await this.save(this.config);
  }
}
