import {Injectable} from '@angular/core';
import {environment} from '../../../environments/environment';
import {HttpClient, HttpParams} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {Category} from '@shared/models/category.model';
import {getObjectiveNumberObjective, isSubObjective, Pentest} from '@shared/models/pentest.model';
import {Store} from '@ngxs/store';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {catchError, map, switchMap} from 'rxjs/operators';
import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function';

@Injectable({
  providedIn: 'root'
})
export class PentestService {

  private apiBaseURL = `${environment.apiEndpoint}/pentests`;

  constructor(
    private http: HttpClient,
    private readonly store: Store) {
  }

  /**
   * Load Pentests
   * @param category the categories of which the pentests should be requested
   */
  // ToDo: Should be refactored to be less ugly fr
  public loadPentests(category: Category): Observable<Pentest[]> {
    return this.store.select(ProjectState.project).pipe(
      switchMap(project => this.getPentestByProjectIdAndCategory(project.id, category)),
      catchError(_ => of(null)),
      map((response: Pentest[]) => {
        // Load template pentest
        const templatePentests = getTempPentestsForCategory(category);
        // The pentests that get returned to the component
        let availablePentests: Pentest[] = response;
        // Add pentest template to complete pentests if not included in request
        if (availablePentests) {
          for (let i = 0; i < templatePentests.length; i++) {
            if (!availablePentests.map(it => it.refNumber).includes(templatePentests[i].refNumber)) {
              availablePentests.push(templatePentests[i]);
              // Loads child entry from response in template when parent is still from template
              if (isSubObjective(availablePentests[i])) {
                const parentObjectiveNumber = getObjectiveNumberObjective(availablePentests[i]);
                const parentTemplatePentestIndex = templatePentests.map(it => it.refNumber).indexOf(parentObjectiveNumber);
                const parentTemplatePentest = templatePentests[parentTemplatePentestIndex];
                parentTemplatePentest.childEntries.forEach((childEntry: Pentest) => {
                  if (childEntry.refNumber === availablePentests[i].refNumber) {
                    const unusedTemplateChildEntryIndex = parentTemplatePentest.childEntries.indexOf(childEntry);
                    // ToDo add the child entry from response here
                    parentTemplatePentest.childEntries[unusedTemplateChildEntryIndex] = availablePentests[i];
                    availablePentests.splice(i, 1);
                  }
                });
              }
            } else if (templatePentests[i].childEntries && templatePentests[i].childEntries.length !== 0) {
              const indexOfPentestWithChildEntries = availablePentests.map(it => it.refNumber).indexOf(templatePentests[i].refNumber);
              availablePentests[indexOfPentestWithChildEntries].childEntries = [];

              templatePentests[i]?.childEntries?.forEach((childEntry: Pentest) => {
                // ToDo: Add only child entrys that are not included in response aka available pentests
                if (!availablePentests.map(it => it.refNumber).includes(childEntry.refNumber)) {
                  availablePentests[indexOfPentestWithChildEntries].childEntries.push(childEntry);
                } else {
                  // Removes the pentest from availablePentests and add it as a child entry
                  const indexOfPentestThatsChildEntry = availablePentests.map(it => it.refNumber).indexOf(childEntry.refNumber);
                  const pentestThatIsChildEntry = availablePentests[indexOfPentestThatsChildEntry];
                  // Adds the child entry from response
                  availablePentests[indexOfPentestWithChildEntries].childEntries.push(pentestThatIsChildEntry);
                  availablePentests.splice(indexOfPentestThatsChildEntry, 1);
                }
              });
            }
          }
        } else {
          availablePentests = templatePentests;
        }
        return availablePentests;
      })
    );
  }

  /**
   * Get Pentests
   * @param projectId the id of the project
   * @param category the categories of which the pentests should be requested
   */
  private getPentestByProjectIdAndCategory(projectId: string, category: Category): Observable<Pentest[]> {
    const queryParams = new HttpParams().append('projectId', projectId).append('category', Category[category]);
    return this.http.get<Pentest[]>(`${this.apiBaseURL}`, {params: queryParams});
  }

  /**
   * Save Pentest
   * @param pentest the information of the Pentest
   */
  public savePentest(projectId: string, pentest: Pentest): Observable<Pentest> {
    return this.http.post<Pentest>(`${this.apiBaseURL}/${projectId}`, pentest);
  }

  /**
   * Update Pentest
   * @param pentest the information of the Pentest
   */
  public updatePentest(pentest: Pentest): Observable<Pentest> {
    return this.http.patch<Pentest>(`${this.apiBaseURL}/${pentest.id}`, pentest);
  }

  /**
   * Disable Objective
   * @param pentestId the id of the Pentest
   */
  public disableObjective(projectId: string, pentestId: string): Observable<Pentest> {
    return this.http.post<Pentest>(`${this.apiBaseURL}/${projectId}/${pentestId}/disable`, null);
  }

  /**
   * Enable Objective
   * @param pentestId the id of the Pentest
   */
  public enableObjective(projectId: string, pentestId: string): Observable<Pentest> {
    return this.http.post<Pentest>(`${this.apiBaseURL}/${projectId}/${pentestId}/enable`, null);
  }
}
