import { Injectable, computed, signal } from '@angular/core';
import { AuthTokenPair, JwtClaims } from '../models/auth-api.model';
import { JwtService } from './jwt.service';

export type SessionStatus = 'unknown' | 'restoring' | 'authenticated' | 'unauthenticated' | 'expired';

const TOKEN_STORAGE_KEY = 'ed25519AdminToken';
const REFRESH_STORAGE_KEY = 'ed25519AdminRefreshToken';
/** Refresh this long before actual expiry, so a request never races an expiring token. */
const REFRESH_SKEW_MS = 60_000;

/**
 * Holds the Ed25519-flow JWT/refresh-token pair and derived claims. Separate
 * from AdminAuthService (Telegram-session state) by design - the two auth
 * mechanisms are not merged until the backend actually ships the Ed25519
 * endpoints and a migration decision is made (see docs/AUTH.md).
 */
@Injectable({ providedIn: 'root' })
export class SessionService {
  private readonly jwt = new JwtService();

  private readonly tokenSignal = signal<string | null>(null);
  private readonly refreshTokenSignal = signal<string | null>(null);
  private readonly claimsSignal = signal<JwtClaims | null>(null);
  private readonly statusSignal = signal<SessionStatus>('unknown');

  readonly token = this.tokenSignal.asReadonly();
  readonly claims = this.claimsSignal.asReadonly();
  readonly status = this.statusSignal.asReadonly();
  readonly isAuthenticated = computed(() => this.statusSignal() === 'authenticated');
  readonly role = computed(() => this.claimsSignal()?.role ?? null);

  private refreshTimer?: ReturnType<typeof setTimeout>;
  private refreshCallback?: () => void;

  /** Called once by AuthService on init to wire up the refresh trigger without a circular DI dependency. */
  onRefreshDue(callback: () => void): void {
    this.refreshCallback = callback;
  }

  /** Restores session state from persisted storage. Returns true if a (possibly expired) session was found. */
  restore(): boolean {
    this.statusSignal.set('restoring');
    const token = this.readStorage(TOKEN_STORAGE_KEY);
    const refreshToken = this.readStorage(REFRESH_STORAGE_KEY);
    if (!token || !refreshToken) {
      this.statusSignal.set('unauthenticated');
      return false;
    }

    const claims = this.jwt.decode(token);
    if (!claims) {
      this.clear();
      return false;
    }

    this.tokenSignal.set(token);
    this.refreshTokenSignal.set(refreshToken);
    this.claimsSignal.set(claims);

    if (this.jwt.isExpired(claims)) {
      this.statusSignal.set('expired');
    } else {
      this.statusSignal.set('authenticated');
      this.scheduleRefresh(claims);
    }
    return true;
  }

  activate(tokens: AuthTokenPair): void {
    const claims = this.jwt.decode(tokens.token);
    if (!claims) {
      throw new Error('Received a malformed JWT from the auth backend.');
    }

    this.tokenSignal.set(tokens.token);
    this.refreshTokenSignal.set(tokens.refreshToken);
    this.claimsSignal.set(claims);
    this.statusSignal.set('authenticated');
    this.writeStorage(TOKEN_STORAGE_KEY, tokens.token);
    this.writeStorage(REFRESH_STORAGE_KEY, tokens.refreshToken);
    this.scheduleRefresh(claims);
  }

  getRefreshToken(): string | null {
    return this.refreshTokenSignal();
  }

  markExpired(): void {
    this.statusSignal.set('expired');
    this.clearRefreshTimer();
  }

  clear(): void {
    this.tokenSignal.set(null);
    this.refreshTokenSignal.set(null);
    this.claimsSignal.set(null);
    this.statusSignal.set('unauthenticated');
    this.removeStorage(TOKEN_STORAGE_KEY);
    this.removeStorage(REFRESH_STORAGE_KEY);
    this.clearRefreshTimer();
  }

  private scheduleRefresh(claims: JwtClaims): void {
    this.clearRefreshTimer();
    const expiresInMs = claims.exp * 1000 - Date.now();
    const refreshInMs = Math.max(expiresInMs - REFRESH_SKEW_MS, 5_000);
    this.refreshTimer = setTimeout(() => this.refreshCallback?.(), refreshInMs);
  }

  private clearRefreshTimer(): void {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
      this.refreshTimer = undefined;
    }
  }

  private readStorage(key: string): string | null {
    return typeof localStorage === 'undefined' ? null : localStorage.getItem(key);
  }

  private writeStorage(key: string, value: string): void {
    if (typeof localStorage !== 'undefined') {
      localStorage.setItem(key, value);
    }
  }

  private removeStorage(key: string): void {
    if (typeof localStorage !== 'undefined') {
      localStorage.removeItem(key);
    }
  }
}
