import { Injectable } from '@angular/core';
import { Observable, throwError, of, from } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { catchError, delay, tap } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
import { Router } from '@angular/router';

import { CredentialsInstructor } from '../structures/credentials-instructor';
import { InstructorRegisterData } from '../structures/instructor-register-data';
import { InstructorData } from '../structures/instructor-data';

import { CredentialsSchool } from '../structures/credentials-school';
import { SchoolRegisterData } from '../structures/school-register-data';
import { SchoolData } from '../structures/school-data';

import { emptyInstructorDataFactory } from '../empty-objects/empty-instructor-data';
import { LoggedUserData } from '../structures/logged-user-data';

import * as Globals from '../globals';

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

  private userData = new BehaviorSubject<LoggedUserData | null>(null);
  userData$ = this.userData.asObservable();

  // stav ukladani instruktoru propojenych s akci
  private schoolSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  schoolSaveStatus$ = this.schoolSaveStatus.asObservable();

  // private schoolData = new BehaviorSubject<SchoolData | null>(null);
  // schoolData$ = this.schoolData.asObservable();

  error422 = new HttpErrorResponse({ status: 422 });
  error403 = new HttpErrorResponse({ status: 403 });
  response200 = new HttpResponse({ status: 200 });

  constructor(private http: HttpClient, private router: Router) { 
  }

  saveStatusReset(): void {
    if(this.schoolSaveStatus.value != Globals.savingStatus.inProgress) {
      this.schoolSaveStatus.next(Globals.savingStatus.none);
    }
  }

  changeUserData(data: LoggedUserData) {
    this.userData.next(data);
  }

  getActualUserData(): LoggedUserData | null {
    return this.userData.value; 
  }

  instructorRoleAtLeast(minimalRole: string): boolean {
    if(this.userData.value?.instructor?.role != undefined) {
      return Globals.roleIsAtLeast(this.userData.value.instructor.role, minimalRole);
    }
    return false;
  } 

  async getLoggedInstructor(): Promise<InstructorData | null> {
    if(this.userData.value != null){
      return new Promise(resolve => { resolve(this.userData.value?.instructor != undefined ? this.userData.value?.instructor : null); });
    }
    var fromServer = await this.getLoggedUserFromServer();
    if(fromServer.instructor != null) {return new Promise(resolve => {resolve(fromServer.instructor); });
    }
    return new Promise(resolve => { resolve(null); });
  }

  async getLoggedSchool(): Promise<SchoolData | null> {
    if(this.userData.value != null){
      return new Promise(resolve => { resolve(this.userData.value?.school != undefined ? this.userData.value?.school : null); });
    }
    var fromServer = await this.getLoggedUserFromServer();
    if(fromServer.school != null) {return new Promise(resolve => {resolve(fromServer.school); });
    }
    return new Promise(resolve => { resolve(null); });
  }

  getLoggedUserFromServer(): Promise<LoggedUserData> {
    return this.http.get<LoggedUserData>(Globals.api.getLoggedUserData).pipe( 
        tap(data => {
            this.userData.next(data);
        }),
        catchError(this.handleError))
        .toPromise();
  }

  clearUserData() {
    this.userData.next(null);
  }

  getUserData(): Observable<LoggedUserData> {
    return this.http.get<LoggedUserData>(Globals.api.getLoggedUserData).pipe(catchError(this.handleError)) 
  }

  logInInstructor(credentials: CredentialsInstructor): Observable<LoggedUserData> {  
    return this.http.post<any>(Globals.api.instructorLogin, credentials, Globals.httpOptions).pipe( tap(data => this.userData.next(data)), catchError(this.handleError));
  }

  registerInstructor(data: InstructorRegisterData): Observable<LoggedUserData> {  
    return this.http.post<any>(Globals.api.instructorRegister, data, Globals.httpOptions).pipe( tap(data => this.userData.next(data)), catchError(this.handleError));
  }

  logInSchool(credentials: CredentialsSchool): Observable<LoggedUserData> {  
    return this.http.post<any>(Globals.api.schoolLogin, credentials, Globals.httpOptions).pipe( tap(data => this.userData.next(data)), catchError(this.handleError));
  }

  registerSchool(data: SchoolRegisterData): Observable<LoggedUserData> {  
    return this.http.post<any>(Globals.api.schoolRegister, data, Globals.httpOptions).pipe( tap(data => this.userData.next(data)), catchError(this.handleError));
  }

  logOut(): Observable<any> {
    return this.http.get<any>(Globals.api.instructorLogout).pipe(catchError(this.handleError));
  }

  actualizeUserData(): void {
    this.http.get<any>(Globals.api.getLoggedUserData).pipe(catchError(this.handleError)).subscribe(
      data => this.userData.next(data)
    );
  }

  async saveSchoolData(data: SchoolData): Promise<SchoolData | null> {
    var result = null;
    this.schoolSaveStatus.next(Globals.savingStatus.inProgress);
    await this.http.post<SchoolData>(Globals.api.updateSchool, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
      (val) => {
        var newData = this.userData.value;
        if(newData?.school != null) {
          newData.school = val;
        }
        this.userData.next(newData);
        this.schoolSaveStatus.next(Globals.savingStatus.success);
        result = val;
      },
      (err) => this.schoolSaveStatus.next(Globals.savingStatus.failed)
    );
    return result;
  }

  // afterLoggedUserDataLoaded(data: LoggedUserData) {
  //   this.userData.next(data);
  // }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong.
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${error.error}`);
    }
    // Return an observable with a user-facing error message.
    return throwError(
      'Something bad happened; please try again later.');
  }
}