/*
 * 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 {HttpClient, HttpResponse} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {HttpClientInterface, HttpClientRequest} from '@openstapps/api';
import {map, retry} from 'rxjs/operators';
import {lastValueFrom, Observable} from 'rxjs';
import {InternetConnectionService} from '../../util/internet-connection.service';

type HttpRequestFunctions = InstanceType<typeof HttpClient>['request'];
type HttpRequestFunction<T extends ReturnType<HttpRequestFunctions>> = Extract<
  HttpRequestFunctions,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  (...parameters: any[]) => T
>;
type HttpRequestParameters<T extends ReturnType<HttpRequestFunctions>> = Parameters<HttpRequestFunction<T>>;

/**
 * HttpClient that is based on the Angular HttpClient (@TODO: move it to provider or independent package)
 */
@Injectable()
export class StAppsWebHttpClient implements HttpClientInterface {
  /**
   *
   */
  constructor(
    private readonly http: HttpClient,
    private readonly connectionService: InternetConnectionService,
  ) {}

  /**
   * Make a request
   * @param requestConfig Configuration of the request
   */
  async request<TYPE_OF_BODY>(requestConfig: HttpClientRequest): Promise<Response<TYPE_OF_BODY>> {
    const request: HttpRequestParameters<Observable<HttpResponse<TYPE_OF_BODY>>> = [
      requestConfig.method || 'GET',
      requestConfig.url.toString(),
      {
        body: (requestConfig.body || {}) as TYPE_OF_BODY,
        headers: requestConfig.headers,
        observe: 'response',
        responseType: 'json',
      },
    ];
    // TODO: cache requests by hashing the parameters.

    const response: Observable<Response<TYPE_OF_BODY>> = this.http.request(...request).pipe(
      retry(this.connectionService.retryConfig),
      map(
        response =>
          Object.assign(response, {
            statusCode: response.status,
            body: response.body || {},
          }) as Response<TYPE_OF_BODY>,
      ),
    );

    return lastValueFrom(response);
  }
}

/**
 * Response with generic for the type of body that is returned from the request
 */
export interface Response<TYPE_OF_BODY> extends HttpResponse<TYPE_OF_BODY> {
  /**
   * TODO
   */
  body: TYPE_OF_BODY;
  /**
   * TODO
   */
  statusCode: number;
}
