import { DestroyRef, Injectable, effect, inject, signal, } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { FastPassPromptComponent } from '../elements/fast-pass-prompt.component';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { outputToObservable } from '@angular/core/rxjs-interop';
import { OAuthService } from 'angular-oauth2-oidc';

export interface EnsureOptions {
  verifyEachTime?: boolean; // default false
  cacheMs?: number; // use with verifyEachTime=false
  promptTitle?: string;
  promptMessage?: string;
}

@Injectable({ providedIn: 'root' })
export class PasswordService {
  private readonly oauthService = inject(OAuthService);
  private readonly baseUrl = `${window.location.origin}/api/password`;
  private http = inject(HttpClient);
  private destroyRef = inject(DestroyRef);
  private overlay = inject(Overlay);
  private overlayRef?: OverlayRef;

  // If you later switch to tokens, keep the API the same and just
  // store a token here instead of a raw password.
  private storageKey = 'app.password.v1';

  // Signals for state
  password = signal<string | null>(null);
  verifying = signal(false);
  lastVerifiedAt = signal<number | null>(null);

  constructor() {
    // Initialize from localStorage
    try {
      const raw = localStorage.getItem(this.storageKey);
      if (raw) this.password.set(JSON.parse(raw));
    }
    catch {
      // ignore
    }

    // Persist to localStorage
    effect(() => {
      const pwd = this.password();
      if (pwd == null) {
        localStorage.removeItem(this.storageKey);
      }
      else {
        localStorage.setItem(this.storageKey, JSON.stringify(pwd));
      }
    });

    // Cross-tab sync
    const onStorage = (e: StorageEvent) => {
      if (e.key === this.storageKey) {
        this.password.set(e.newValue ? JSON.parse(e.newValue) : null);
      }
    }
      ;
    window.addEventListener('storage', onStorage);
    this.destroyRef.onDestroy(() =>
      window.removeEventListener('storage', onStorage)
    );
  }

  setPassword(p: string) {
    this.password.set(p);
  }

  clear() {
    this.password.set(null);
    this.lastVerifiedAt.set(null);
  }

  async validateWithServer(pwd: string): Promise<boolean> {
    this.verifying.set(true);
    try {
      const url = `${this.baseUrl}/verify`;
      await firstValueFrom(
        this.http.post(
          url,
          { password: pwd },
          { responseType: 'text' }
        )
      );
      this.lastVerifiedAt.set(Date.now());
      return true;
    } catch (err: unknown) {
      if (
        typeof err === 'object' &&
        err !== null &&
        'status' in err &&
        (err as { status?: number }).status !== undefined &&
        ((err as { status?: number }).status === 401 ||
          (err as { status?: number }).status === 403)
      ) {
        return false;
      }
      throw err;
    } finally {
      this.verifying.set(false);
    }
  }

  // Main API: ensure we have a valid password.
  // - If stored, tries server validation (or uses cache if configured).
  // - If missing/invalid, opens a dialog to collect and verify it.
  async ensureValid(options?: EnsureOptions): Promise<boolean> {
    const opts: Required<EnsureOptions> = {
      verifyEachTime: false,
      cacheMs: 600000, //10 minutes
      promptTitle: options?.promptTitle ?? 'Enter password',
      promptMessage:
        options?.promptMessage ?? 'This action requires a password.',
    }
      ;

    // Optional cache to reduce server hits
    if (!opts.verifyEachTime && opts.cacheMs > 0) {
      const last = this.lastVerifiedAt();
      if (last && Date.now() - last < opts.cacheMs && this.password()) {
        return true;
      }
    }

    const existing = this.password();
    if (existing) {
      const ok = await this.validateWithServer(existing);
      if (ok) return true;
      this.clear(); // Stored password is no longer valid
    }

    // Prompt user via dialog
    const ok = await this.openPasswordDialog();
    return ok;
  }

  private async openPasswordDialog(): Promise<boolean> {
    this.overlayRef = this.overlay.create();
    const portal = new ComponentPortal(FastPassPromptComponent);
    const componentRef = this.overlayRef.attach(portal);

    const success = await firstValueFrom(outputToObservable(componentRef.instance.closed));
    this.overlayRef?.dispose();
    this.overlayRef = undefined;

    return !!success;
  }

  // Helper to wrap any UI handler:
  // const onClick = passwordService.guard(() => this.doSomething());
  guard<A extends unknown[]>(
    fn: (...args: A) => void | Promise<void>,
    options?: EnsureOptions
  ) {
    return async (...args: A) => {
      const ok = await this.ensureValid(options);
      if (ok) await fn(...args);
    };
  }

  private clearDomainCookies(): void {
    const cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i];
      const eqPos = cookie.indexOf('=');
      const name = eqPos > -1 ? cookie.substring(0, eqPos) : cookie;
      // Set the cookie's expiration to a past date, and ensure the path is also considered.
      // Cookies are path-dependent, so setting a cookie with an empty path often clears it
      // for all paths if it was set for the root ('/')
      document.cookie = name.trim() + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;';
      // If you know specific paths, you might need to add more specific path values:
      // document.cookie = name.trim() + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/my-specific-path;';
    }
  }

  logout() {
    // const auth = new AuthClass(null);
    // auth.signOut();

    localStorage.clear();
    sessionStorage.clear();
    this.clearDomainCookies();
    this.oauthService.revokeTokenAndLogout();
    this.oauthService.logOut();
    // Call backend to clear the cookie as well
    // const logoutCookieEndpoint = `${window.location.origin}/api/Auth/LogoutCookie`;
    // this.http.get(logoutCookieEndpoint).pipe(take(1)).subscribe({
    //   next: () => {
    //     console.log('Backend auth cookie cleared.');
    //     this.oauthService.logOut();
    //   },
    //   error: err => console.error('Error clearing backend cookie:  ', err)
    // });
  }

}

