import { HttpClient } from '@angular/common/http';
import {
  effect,
  inject,
  Injectable,
  signal,
  WritableSignal,
} from '@angular/core';
import { LoginResponse, User, Claims } from '../interfaces/auth';
import { environment } from '../../../environments/environment';

const JWT_KEY = 'token';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private http = inject(HttpClient);
  jwt: WritableSignal<string | null> = signal(null);
  claims: WritableSignal<Claims | null> = signal(null);
  timeout: any | null = null;

  constructor() {
    // read from localStorage
    const token = window.localStorage.getItem(JWT_KEY);
    if (token) {
      this.jwt.set(token);
    }

    // update claims
    effect(() => {
      const token = this.jwt();
      if (!token) {
        this.claims.set(null);
        return;
      }
      this.claims.set(parseJwt(token));
    });

    // set localStorage when this.jwt changes
    effect(() => {
      const token = this.jwt();
      if (token) {
        window.localStorage.setItem(JWT_KEY, token);
        return;
      }
      window.localStorage.removeItem(JWT_KEY);
    });

    // if token expired -> set this.jwt to null
    effect(() => {
      const token = this.jwt();
      if (!token) {
        return;
      }

      const remainingMilli =
        extractExpiration(token).valueOf() - new Date().valueOf();

      // if expired set to null
      if (remainingMilli < 0) {
        this.clearTimeout();
        this.jwt.set(null);
        return;
      }

      this.clearTimeout();
      this.timeout = setTimeout(() => {
        this.jwt.set(null);
      }, remainingMilli);
    });
  }

  login(user: User) {
    this.http
      .post<LoginResponse>(`${environment.apiRoot}/login`, user)
      .subscribe((res) => this.jwt.set(res.token));
  }
  logout() {
    this.http.delete(`${environment.apiRoot}/logout`).subscribe(() => {
      this.jwt.set(null);
    });
  }

  private clearTimeout() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
  }
}

// https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
function parseJwt(token: string): Claims {
  var base64Url = token.split('.')[1];
  var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  var jsonPayload = decodeURIComponent(
    window
      .atob(base64)
      .split('')
      .map(function (c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
      })
      .join(''),
  );

  return JSON.parse(jsonPayload);
}

function extractExpiration(token: string): Date {
  const jwt = parseJwt(token);
  return new Date(jwt.exp * 1000);
}
