import { Injectable, Provider } from '@angular/core';
import { 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 } 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 {
  NotExistingUser,
  User,
  UserCreateInput,
  UserUpdateInput,
} from 'src/app/model/auth/user';
import { Role } from 'src/app/model/entities/role';
import { DbModel } from 'src/app/model/entities/db-model';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';

@Injectable()
export class UsersDataService
  extends PageWithHistoryDataService<User>
  implements
    Create<User, UserCreateInput>,
    Update<User, UserUpdateInput>,
    Delete<User>,
    Restore<User>
{
  constructor(
    private spinner: SpinnerService,
    private apollo: Apollo
  ) {
    super();
  }

  load(
    filter?: Filter[] | undefined,
    sort?: Sort[] | undefined,
    skip?: number | undefined,
    take?: number | undefined
  ): Observable<QueryResult<User>> {
    return this.apollo
      .query<{ users: QueryResult<User> }>({
        query: gql`
          query getUsers(
            $filter: [UserFilter]
            $sort: [UserSort]
            $skip: Int
            $take: Int
          ) {
            users(filter: $filter, sort: $sort, skip: $skip, take: $take) {
              count
              totalCount
              nodes {
                id
                keycloakId
                username
                email

                roles {
                  id
                  name
                }

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          filter: filter,
          sort: sort,
          skip: skip,
          take: take,
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data.users));
  }

  loadById(id: number): Observable<User> {
    return this.apollo
      .query<{ users: QueryResult<User> }>({
        query: gql`
          query getSingleUser($id: Int) {
            users(filter: { id: { equal: $id } }) {
              count
              totalCount
              nodes {
                id
                keycloakId
                username
                email
                roles {
                  id
                  name
                }

                ...DB_MODEL
              }
            }
          }

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

  loadHistory(id: number): Observable<DbModel[]> {
    return this.apollo
      .query<{ users: QueryResult<User> }>({
        query: gql`
          query getUserHistory($id: Int) {
            users(filter: { id: { equal: $id } }) {
              count
              totalCount
              nodes {
                history {
                  id
                  keycloakId
                  username
                  email
                  roles {
                    name
                  }

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

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

  create(object: UserCreateInput): Observable<User | undefined> {
    return this.apollo
      .mutate<{ user: { create: User } }>({
        mutation: gql`
          mutation createUser($input: UserCreateInput!) {
            user {
              create(input: $input) {
                id
                roles {
                  name
                }

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          input: {
            keycloakId: object.keycloakId,
            roles: object.roles?.map(x => x.id),
          },
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.user.create));
  }

  update(object: UserUpdateInput): Observable<User | undefined> {
    return this.apollo
      .mutate<{ user: { update: User } }>({
        mutation: gql`
          mutation updateUser($input: UserUpdateInput!) {
            user {
              update(input: $input) {
                id
                roles {
                  name
                }

                ...DB_MODEL
              }
            }
          }

          ${DB_MODEL_FRAGMENT}
        `,
        variables: {
          input: {
            id: object.id,
            roles: object.roles?.map(x => x.id),
          },
        },
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data?.user.update));
  }

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

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

  getAllRoles(): Observable<Role[]> {
    return this.apollo
      .query<{ roles: QueryResult<Role> }>({
        query: gql`
          query getRoles {
            roles {
              nodes {
                id
                name
              }
            }
          }
        `,
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data.roles.nodes));
  }

  getNotExistingUsersFromKeycloak(): Observable<NotExistingUser[]> {
    return this.apollo
      .query<{ notExistingUsersFromKeycloak: NotExistingUser[] }>({
        query: gql`
          query getNotExistingUsers {
            notExistingUsersFromKeycloak {
              keycloakId
              username
            }
          }
        `,
      })
      .pipe(
        catchError(err => {
          this.spinner.hide();
          throw err;
        })
      )
      .pipe(map(result => result.data.notExistingUsersFromKeycloak));
  }

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