import { Injectable } from '@angular/core';
import { HotToastService } from '@ngxpert/hot-toast';
import {
  catchError,
  combineLatest,
  map,
  Observable,
  retry,
  shareReplay,
  startWith,
  Subject,
  switchMap,
  tap,
  throwError,
  timer,
} from 'rxjs';
import {
  GetTicketsGQL,
  GetTicketsQuery,
  GetTicketStateMetaGQL,
  GetTicketStateMetaQuery,
  SearchTicketsGQL,
  SearchTicketsQuery,
  SetDocumentInfoGQL,
  SetDocumentInfoMutationVariables,
  TicketFilterArgs,
  TicketStateKpiGQL,
  TicketStateKpiQuery,
  TicketValidationState,
} from '../../core/data-access/graphql/generated/generated';

@Injectable({
  providedIn: 'root',
})
export class TicketsService {
  ticketsUpdated$ = timer(60 * 1000).pipe(
    tap((value) => {
      if (value) {
        this.toast.info('Daten wurden aktualisiert', {
          id: 'update',
        });
      }
    }),
    shareReplay(1),
    startWith(undefined)
  );

  updateTickets$ = new Subject<void>();

  constructor(
    private readonly getTicketsGql: GetTicketsGQL,
    private readonly getTicketStateMetaGql: GetTicketStateMetaGQL,
    private readonly searchTicketsGql: SearchTicketsGQL,
    private readonly setDocumentInfoGql: SetDocumentInfoGQL,
    private readonly ticketStateKpiGql: TicketStateKpiGQL,
    private readonly toast: HotToastService
  ) {
    this.updateTickets$.next();
  }

  fetchTicketsByState(
    state: TicketValidationState,
    cursor: string,
    filters: TicketFilterArgs,
    take = 20
  ): Observable<GetTicketsQuery['tickets']> {
    const ticketsQuery = this.getTicketsGql.watch({
      state,
      cursor,
      filters,
      take,
    });

    const ticketsUpdate = combineLatest([
      this.ticketsUpdated$,
      this.updateTickets$,
    ]).pipe(
      switchMap(() => ticketsQuery.refetch()),
      startWith(undefined)
    );

    return combineLatest([ticketsUpdate, ticketsQuery.valueChanges]).pipe(
      catchError((error) => {
        console.error(error);
        return throwError(() => error);
      }),
      retry({ delay: 1000, count: 3 }),
      map(([updatedData, firstData]) =>
        updatedData ? updatedData : firstData
      ),
      map((response) => response.data.tickets)
    );
  }

  fetchTicketStateMeta(
    state: TicketValidationState
  ): Observable<GetTicketStateMetaQuery['ticketStateMeta']> {
    const ticketStateMetaQuery = this.getTicketStateMetaGql.watch({
      state,
    });

    const ticketsUpdate = combineLatest([
      this.ticketsUpdated$,
      this.updateTickets$,
    ]).pipe(
      switchMap(() => ticketStateMetaQuery.refetch()),
      startWith(undefined)
    );

    return combineLatest([
      ticketsUpdate,
      ticketStateMetaQuery.valueChanges,
    ]).pipe(
      catchError((error) => {
        console.error(error);
        return throwError(() => error);
      }),
      retry({ delay: 1000, count: 3 }),
      map(([updatedData, firstData]) =>
        updatedData ? updatedData : firstData
      ),
      map((response) => response.data.ticketStateMeta)
    );
  }

  fetchTicketStateKpi(
    state: TicketValidationState
  ): Observable<TicketStateKpiQuery['ticketStateKpi']> {
    const ticketStateKpiQuery = this.ticketStateKpiGql.watch({
      state,
    });

    const ticketsUpdate = combineLatest([
      this.ticketsUpdated$,
      this.updateTickets$,
    ]).pipe(
      switchMap(() => ticketStateKpiQuery.refetch()),
      startWith(undefined)
    );

    return combineLatest([
      ticketsUpdate,
      ticketStateKpiQuery.valueChanges,
    ]).pipe(
      catchError((error) => {
        console.error(error);
        return throwError(() => error);
      }),
      retry({ delay: 1000, count: 3 }),
      map(([updatedData, firstData]) =>
        updatedData ? updatedData : firstData
      ),
      map((response) => response.data.ticketStateKpi)
    );
  }

  searchTickets(
    query: string
  ): Observable<SearchTicketsQuery['searchTickets']> {
    return this.searchTicketsGql
      .fetch({ query })
      .pipe(map((response) => response.data.searchTickets));
  }

  setDocumentInfo(
    ticketId: string,
    { approval, tdLocation }: Omit<SetDocumentInfoMutationVariables, 'ticketId'>
  ) {
    if ((approval as any)?.['__typename'])
      delete (approval as any)['__typename'];

    return this.setDocumentInfoGql
      .mutate({
        ticketId,
        approval,
        tdLocation,
      })
      .pipe(
        map((res) => res.data?.setDocumentInfo),
        tap(() => this.updateTickets$.next())
      );
  }
}
