/*
 * Copyright (C) 2019-2022 Open 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 {SCMap} from '../general/map.js';
import {SCErrorResponse} from './error.js';
import {SCIndexRequest, SCIndexResponse, SCIndexRoute} from './routes/index.js';
import {
  SCBookAvailabilityRequest,
  SCBookAvailabilityResponse,
  SCBookAvailabilityRoute,
} from './routes/book-availability.js';
import {SCBulkAddRequest, SCBulkAddResponse, SCBulkAddRoute} from './routes/bulk-add.js';
import {SCBulkDoneRequest, SCBulkDoneResponse, SCBulkDoneRoute} from './routes/bulk-done.js';
import {SCBulkRequest, SCBulkResponse, SCBulkRoute} from './routes/bulk-request.js';
import {SCFeedbackRequest, SCFeedbackResponse, SCFeedbackRoute} from './routes/feedback.js';
import {SCSearchRequest, SCSearchResponse, SCSearchRoute} from './routes/search.js';
import {SCMultiSearchRequest, SCMultiSearchResponse, SCMultiSearchRoute} from './routes/search-multi.js';
import {SCThingUpdateRequest, SCThingUpdateResponse, SCThingUpdateRoute} from './routes/thing-update.js';
import {SCRatingRequest, SCRatingResponse, SCRatingRoute} from './routes/rating.js';

/**
 * Possible Verbs for HTTP requests
 */
export enum SCRouteHttpVerbs {
  GET = 'GET',
  POST = 'POST',
  PUT = 'PUT',
}

/**
 * The constructor of an error response
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type SCErrorResponseConstructor = new (...arguments_: any[]) => SCErrorResponse;

/**
 * A description of a route
 */
export interface SCRoute {
  /**
   * A map of names of possible errors that can be returned by the route with their appropriate status codes
   */
  errorNames: SCErrorResponseConstructor[];

  /**
   * HTTP verb to use to request the route
   */
  method: SCRouteHttpVerbs;

  /**
   * Map of obligatory parameters and their type that have to be set via the requested path
   */
  obligatoryParameters?: SCMap<string>;

  /**
   * Name of the type of the request body
   */
  requestBodyName: string;

  /**
   * Name of the type of the response body
   */
  responseBodyName: string;

  /**
   * Status code for success
   */
  statusCodeSuccess: number;

  /**
   * URL path of the route
   */
  urlPath: string;
}

/**
 * An abstract route
 */
export abstract class SCAbstractRoute implements SCRoute {
  /**
   * @see SCRoute.errorNames
   */
  errorNames: SCErrorResponseConstructor[] = [];

  /**
   * @see SCRoute.method
   */
  method: SCRouteHttpVerbs = SCRouteHttpVerbs.GET;

  /**
   * @see SCRoute.obligatoryParameters
   */
  obligatoryParameters?: SCMap<string>;

  /**
   * @see SCRoute.requestBodyName
   */
  requestBodyName = 'any';

  /**
   * @see SCRoute.responseBodyName
   */
  responseBodyName = 'any';

  /**
   * @see SCRoute.statusCodeSuccess
   */
  statusCodeSuccess = 200;

  /**
   * @see SCRoute.urlPath
   */
  urlPath = '/';

  /**
   * Get "compiled" URL path
   * @param parameters Parameters to compile URL path with
   */
  public getUrlPath(parameters: SCMap<string> = {}): string {
    let obligatoryParameters: string[] = [];

    if (typeof this.obligatoryParameters === 'object') {
      obligatoryParameters = Object.keys(this.obligatoryParameters);
    }

    if (Object.keys(parameters).length > obligatoryParameters.length) {
      throw new Error('Extraneous parameters provided.');
    }

    return this.urlPath
      .split('/')
      .map(part => {
        if (part.indexOf(':') !== 0) {
          return part;
        }

        const parameter = part.slice(1);

        if (parameters[parameter] === undefined) {
          throw new TypeError(`Parameter '${parameter}' not provided.`);
        }

        return parameters[parameter];
      })
      .join('/');
  }
}

/**
 * Possible requests
 */
export type SCRequests =
  | SCBookAvailabilityRequest
  | SCBulkRequest
  | SCBulkAddRequest
  | SCBulkDoneRequest
  | SCFeedbackRequest
  | SCRatingRequest
  | SCIndexRequest
  | SCMultiSearchRequest
  | SCSearchRequest
  | SCThingUpdateRequest;

/**
 * Possible responses
 */
export type SCResponses =
  | SCBookAvailabilityResponse
  | SCBulkResponse
  | SCBulkAddResponse
  | SCBulkDoneResponse
  | SCFeedbackResponse
  | SCRatingResponse
  | SCIndexResponse
  | SCMultiSearchResponse
  | SCSearchResponse
  | SCThingUpdateResponse;

/**
 * Associated response for a request
 */
export type SCAssociatedResponse<REQUEST> = REQUEST extends SCBookAvailabilityRequest
  ? SCBookAvailabilityResponse
  : REQUEST extends SCBulkRequest
  ? SCBulkResponse
  : REQUEST extends SCBulkAddRequest
  ? SCBulkAddResponse
  : REQUEST extends SCBulkDoneRequest
  ? SCBulkDoneResponse
  : REQUEST extends SCFeedbackRequest
  ? SCFeedbackResponse
  : REQUEST extends SCRatingRequest
  ? SCRatingResponse
  : REQUEST extends SCIndexRequest
  ? SCIndexResponse
  : REQUEST extends SCMultiSearchRequest
  ? SCMultiSearchResponse
  : REQUEST extends SCSearchRequest
  ? SCSearchResponse
  : REQUEST extends SCThingUpdateRequest
  ? SCThingUpdateResponse
  : never;

/**
 * Associated request for a response
 */
export type SCAssociatedRequest<RESPONSE> = RESPONSE extends SCBookAvailabilityResponse
  ? SCBookAvailabilityRequest
  : RESPONSE extends SCBulkResponse
  ? SCBulkRequest
  : RESPONSE extends SCBulkAddResponse
  ? SCBulkAddRequest
  : RESPONSE extends SCBulkDoneResponse
  ? SCBulkDoneRequest
  : RESPONSE extends SCFeedbackResponse
  ? SCFeedbackRequest
  : RESPONSE extends SCRatingResponse
  ? SCRatingRequest
  : RESPONSE extends SCIndexResponse
  ? SCIndexRequest
  : RESPONSE extends SCMultiSearchResponse
  ? SCMultiSearchRequest
  : RESPONSE extends SCSearchResponse
  ? SCSearchRequest
  : RESPONSE extends SCThingUpdateResponse
  ? SCThingUpdateRequest
  : never;

/**
 * Associated request for a route
 */
export type SCAssignedRequest<ROUTE extends SCAbstractRoute> = ROUTE extends SCBookAvailabilityRoute
  ? SCBookAvailabilityRequest
  : ROUTE extends SCBulkRoute
  ? SCBulkRequest
  : ROUTE extends SCBulkAddRoute
  ? SCBulkAddRequest
  : ROUTE extends SCBulkDoneRoute
  ? SCBulkDoneRequest
  : ROUTE extends SCFeedbackRoute
  ? SCFeedbackRequest
  : ROUTE extends SCRatingRoute
  ? SCRatingRequest
  : ROUTE extends SCIndexRoute
  ? SCIndexRequest
  : ROUTE extends SCMultiSearchRoute
  ? SCMultiSearchRequest
  : ROUTE extends SCSearchRoute
  ? SCSearchRequest
  : ROUTE extends SCThingUpdateRoute
  ? SCThingUpdateRequest
  : never;

/**
 * Associated response for a route
 */
export type SCAssignedResponse<ROUTE extends SCAbstractRoute> = ROUTE extends SCBookAvailabilityRoute
  ? SCBookAvailabilityResponse
  : ROUTE extends SCBulkRoute
  ? SCBulkResponse
  : ROUTE extends SCBulkAddRoute
  ? SCBulkAddResponse
  : ROUTE extends SCBulkDoneRoute
  ? SCBulkDoneResponse
  : ROUTE extends SCFeedbackRoute
  ? SCFeedbackResponse
  : ROUTE extends SCRatingRoute
  ? SCRatingResponse
  : ROUTE extends SCIndexRoute
  ? SCIndexResponse
  : ROUTE extends SCMultiSearchRoute
  ? SCMultiSearchResponse
  : ROUTE extends SCSearchRoute
  ? SCSearchResponse
  : ROUTE extends SCThingUpdateRoute
  ? SCThingUpdateResponse
  : never;
