import { Injectable, PipeTransform } from '@angular/core';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { switchMap, debounceTime, tap, map, catchError } from 'rxjs/operators';
import { DecimalPipe } from '@angular/common';
import { User } from './user';
import { BaseService } from 'src/app/shared/service/base.service';

interface SearchResult {
  users: User[];
  total: number;
}

interface State {
  page: number;
  pageSize: number;
  searchTerm: string;
  sortColumn: string;
  sortDirection: string;
}

const compare = (v1: string | number, v2: string | number) => (v1 < v2 ? -1 : v1 > v2 ? 1 : 0);

function sort(users: User[], column: string, direction: string): User[] {
  if (direction === '' || column === '') {
    return users;
  } else {
    return [...users].sort((a, b) => {
      const res = compare(a[column], b[column]);
      return direction === 'asc' ? res : -res;
    });
  }
}

function matches(user: User, term: string, pipe: PipeTransform) {
  return user.name.toLowerCase().includes(term.toLowerCase())
    || user.lastName.toLowerCase().includes(term.toLowerCase())
    || user.email.toLowerCase().includes(term.toLowerCase());
}

@Injectable({ providedIn: 'root' })
export class UserService {
  private _loading$ = new BehaviorSubject<boolean>(true);
  private _search$ = new Subject<void>();
  private _users$ = new BehaviorSubject<User[]>([]);
  private _total$ = new BehaviorSubject<number>(0);

  private _state: State = {
    page: 1,
    pageSize: 10,
    searchTerm: '',
    sortColumn: '',
    sortDirection: ''
  };

  constructor(private pipe: DecimalPipe, private baseService: BaseService) {
    this._search$.pipe(
      tap(() => this._loading$.next(true)),
      debounceTime(200),
      switchMap(() => this._fetchUsers()),
      tap(() => this._loading$.next(false))
    ).subscribe(result => {
      this._users$.next(result.users);
      this._total$.next(result.total);
    });

    this._search$.next();
  }

  fetchUsers() {
    this._search$.next();
  }

  private _fetchUsers(): Observable<SearchResult> {
    return this.baseService.getData('user/getAllUsers').pipe(
      map((data: any) => {        

        const currentUser = JSON.parse(localStorage.getItem('currentUser'));
        const filteredUsers = data.users.filter(user => user.email !== currentUser.email);
        const { searchTerm, sortColumn, sortDirection, pageSize, page } = this._state;

        // 1. Filter
        let users = filteredUsers.filter(user => matches(user, searchTerm, this.pipe));

        // 2. Sort
        users = sort(users, sortColumn, sortDirection);

        // 3. Paginate
        const total = users.length;
        users = users.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);

        return { users, total };
      }),
      catchError(() => of({ users: [], total: 0 }))
    );
  }

  get users$(): Observable<User[]> { return this._users$.asObservable(); }
  get total$(): Observable<number> { return this._total$.asObservable(); }
  get loading$(): Observable<boolean> { return this._loading$.asObservable(); }

  get page(): number { return this._state.page; }
  set page(page: number) { this._set({ page }); }

  get pageSize(): number { return this._state.pageSize; }
  set pageSize(pageSize: number) { this._set({ pageSize }); }

  get searchTerm(): string { return this._state.searchTerm; }
  set searchTerm(searchTerm: string) { this._set({ searchTerm }); }

  set sortColumn(sortColumn: string) { this._set({ sortColumn }); }
  set sortDirection(sortDirection: string) { this._set({ sortDirection }); }

  private _set(patch: Partial<State>) {
    Object.assign(this._state, patch);
    this._search$.next();
  }
}
