import { Injectable, Provider } from '@angular/core';
import { merge, Observable } from 'rxjs';
import {
  Create,
  Delete,
  PageDataService,
  Restore,
  Update,
} from 'src/app/core/base/page.data.service';
import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort, SortOrder } from 'src/app/model/graphql/filter/sort.model';
import { Apollo, gql } from 'apollo-angular';
import { QueryResult } from 'src/app/model/entities/query-result';
import {
  DB_HISTORY_MODEL_FRAGMENT,
  DB_MODEL_FRAGMENT,
} from 'src/app/model/graphql/db-model.query';
import { catchError, map } from 'rxjs/operators';
import { SpinnerService } from 'src/app/service/spinner.service';
import {
  ShortUrl,
  ShortUrlCreateInput,
  ShortUrlUpdateInput,
} from 'src/app/model/entities/short-url';
import { Group } from 'src/app/model/entities/group';
import { Domain } from 'src/app/model/entities/domain';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
import { SidebarService } from 'src/app/service/sidebar.service';

@Injectable()
export class ShortUrlsDataService
  extends PageDataService<ShortUrl>
  implements
    Create<ShortUrl, ShortUrlCreateInput>,
    Update<ShortUrl, ShortUrlUpdateInput>,
    Delete<ShortUrl>,
    Restore<ShortUrl>
{
  constructor(
    private spinner: SpinnerService,
    private apollo: Apollo,
    private sidebarService: SidebarService
  ) {
    super();
  }

  load(
    filter?: Filter[] | undefined,
    sort?: Sort[] | undefined,
    skip?: number | undefined,
    take?: number | undefined
  ): Observable<QueryResult<ShortUrl>> {
    return this.apollo
      .query<{
        shortUrls: QueryResult<ShortUrl>;
        shortUrlsWithoutGroup: QueryResult<ShortUrl>;
      }>({
        query: gql`
          query getShortUrls(
            $filter: [ShortUrlFilter]
            $filter2: [ShortUrlFilter]
            $sort: [ShortUrlSort]
          ) {
            shortUrls(filter: $filter, sort: $sort) {
              nodes {
                id
                shortUrl
                targetUrl
                description
                loadingScreen
                visits
                group {
                  id
                  name
                }
                domain {
                  id
                  name
                }

                ...DB_MODEL
              }
            }

            shortUrlsWithoutGroup: shortUrls(filter: $filter2, sort: $sort) {
              nodes {
                id
                shortUrl
                targetUrl
                description
                loadingScreen
                visits
                group {
                  id
                  name
                }
                domain {
                  id
                  name
                }

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          filter: [
            {
              userSpace: {
                id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
              },
            },
            { group: { deleted: { equal: false } } },
            {
              group: {
                userSpace: {
                  id: {
                    equal: this.sidebarService.selectedUserSpace$.value?.id,
                  },
                },
              },
            },
            ...(filter ?? []),
          ],
          filter2: [
            {
              userSpace: {
                id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
              },
            },
            {
              group: {
                isNull: true,
              },
            },
            ...(filter ?? []),
          ],
          sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
          skip,
          take,
        },
      })
      .pipe(
        map(x => {
          return {
            count: x.data.shortUrls.count + x.data.shortUrlsWithoutGroup.count,
            totalCount:
              x.data.shortUrls.totalCount +
              x.data.shortUrlsWithoutGroup.totalCount,
            nodes: [
              ...x.data.shortUrls.nodes,
              ...x.data.shortUrlsWithoutGroup.nodes,
            ],
          };
        })
      );
  }

  loadById(id: number): Observable<ShortUrl> {
    return this.apollo
      .query<{ shortUrls: QueryResult<ShortUrl> }>({
        query: gql`
          query getShortUrl($id: Int) {
            shortUrls(filter: { id: { equal: $id } }) {
              nodes {
                id
                shortUrl
                targetUrl
                description
                loadingScreen
                visits
                group {
                  id
                  name
                }
                domain {
                  id
                  name
                }

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          id: id,
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data.shortUrls.nodes[0]));
  }

  loadHistory(id: number) {
    return this.apollo
      .query<{ shortUrls: QueryResult<ShortUrl> }>({
        query: gql`
          query getDomainHistory($id: Int) {
            shortUrls(filter: { id: { equal: $id } }) {
              count
              totalCount
              nodes {
                history {
                  id
                  shortUrl
                  targetUrl
                  description
                  loadingScreen
                  visits
                  group {
                    id
                    name
                  }
                  domain {
                    id
                    name
                  }

                  ...DB_HISTORY_MODEL
                }
              }
            }
          }

          ${DB_HISTORY_MODEL_FRAGMENT}
        `,
        variables: {
          id,
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.shortUrls?.nodes?.[0]?.history ?? []));
  }

  onChange(): Observable<void> {
    return merge(
      this.apollo
        .subscribe<{ shortUrlChange: void }>({
          query: gql`
            subscription onShortUrlChange {
              shortUrlChange
            }
          `,
        })
        .pipe(map(result => result.data?.shortUrlChange)),

      this.apollo
        .subscribe<{ groupChange: void }>({
          query: gql`
            subscription onGroupChange {
              groupChange
            }
          `,
        })
        .pipe(map(result => result.data?.groupChange))
    ).pipe(map(() => {}));
  }

  create(object: ShortUrlCreateInput): Observable<ShortUrl | undefined> {
    return this.apollo
      .mutate<{ shortUrl: { create: ShortUrl } }>({
        mutation: gql`
          mutation createShortUrl($input: ShortUrlCreateInput!) {
            shortUrl {
              create(input: $input) {
                id
                description

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          input: {
            userSpaceId: this.sidebarService.selectedUserSpace$.value?.id,
            shortUrl: object.shortUrl,
            targetUrl: object.targetUrl,
            description: object.description,
            loadingScreen: object.loadingScreen,
            groupId: object.groupId,
            domainId: object.domainId,
          },
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.shortUrl.create));
  }

  update(object: ShortUrlUpdateInput): Observable<ShortUrl | undefined> {
    return this.apollo
      .mutate<{ shortUrl: { update: ShortUrl } }>({
        mutation: gql`
          mutation updateShortUrl($input: ShortUrlUpdateInput!) {
            shortUrl {
              update(input: $input) {
                id
                description

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          input: {
            id: object.id,
            shortUrl: object.shortUrl,
            targetUrl: object.targetUrl,
            description: object.description,
            loadingScreen: object.loadingScreen,
            groupId: object.groupId,
            domainId: object.domainId,
          },
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.shortUrl.update));
  }

  delete(object: ShortUrl): Observable<boolean> {
    return this.apollo
      .mutate<{ shortUrl: { delete: boolean } }>({
        mutation: gql`
          mutation deleteShortUrl($id: Int!) {
            shortUrl {
              delete(id: $id)
            }
          }
        `,
        variables: {
          id: object.id,
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.shortUrl.delete ?? false));
  }

  restore(object: ShortUrl): Observable<boolean> {
    return this.apollo
      .mutate<{ shortUrl: { restore: boolean } }>({
        mutation: gql`
          mutation restoreShortUrl($id: Int!) {
            shortUrl {
              restore(id: $id)
            }
          }
        `,
        variables: {
          id: object.id,
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.shortUrl.restore ?? false));
  }

  getAllAvailableGroups() {
    return this.apollo
      .query<{ groups: QueryResult<Group> }>({
        query: gql`
          query getGroups($filter: [GroupFilter]) {
            groups(filter: $filter) {
              nodes {
                id
                name
              }
            }
          }
        `,
        variables: {
          filter: [
            {
              userSpace: {
                id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
              },
            },
            { deleted: { equal: false } },
          ],
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data.groups.nodes));
  }

  getAllDomains() {
    return this.apollo
      .query<{ domains: QueryResult<Domain> }>({
        query: gql`
          query getGroups {
            domains {
              nodes {
                id
                name
              }
            }
          }
        `,
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data.domains.nodes));
  }

  static provide(): Provider[] {
    return [
      {
        provide: PageDataService,
        useClass: ShortUrlsDataService,
      },
      {
        provide: PageWithHistoryDataService,
        useClass: ShortUrlsDataService,
      },
      ShortUrlsDataService,
    ];
  }
}
