import { config } from 'dotenv';
import { ConfidentialClientApplication } from '@azure/msal-node';
import PocketBase from 'pocketbase';
import { GraphTokenCache, BackendAuthConfig } from './types';

// Load environment variables from shared secrets directory
config({ path: '/home/admin/secrets/.env' });

/**
 * Configuration Manager
 * Exposes frontend-safe configuration loaded from environment
 */
export class AuthConfigManager {
  /**
   * Get frontend configuration (safe to expose to browser)
   */
  static getFrontendConfig() {
    return {
      pbUrl: process.env.PB_URL!,
      provider: 'microsoft',
      collection: 'Users',
    };
  }

  /**
   * Get all backend secrets (never expose to frontend)
   */
  static getBackendConfig() {
    return {
      clientId: process.env.CLIENT_ID!,
      tenantId: process.env.TENANT_ID!,
      clientSecret: process.env.CLIENT_SECRET!,
      pbDb: process.env.PB_DB!,
    };
  }
}

/**
 * Microsoft Graph Token Management (Backend)
 * Handles token acquisition and caching for backend Graph API calls
 */
export class GraphTokenManager {
  private cca: ConfidentialClientApplication;
  private cache: GraphTokenCache | null = null;
  private config: Required<BackendAuthConfig>;

  constructor(config: BackendAuthConfig) {
    this.config = {
      clientId: config.clientId || process.env.CLIENT_ID || '',
      tenantId: config.tenantId || process.env.TENANT_ID || '',
      clientSecret: config.clientSecret || process.env.CLIENT_SECRET || '',
    };

    this.cca = new ConfidentialClientApplication({
      auth: {
        clientId: this.config.clientId,
        authority: `https://login.microsoftonline.com/${this.config.tenantId}`,
        clientSecret: this.config.clientSecret,
      },
    });
  }

  /**
   * Get Graph token (from cache or acquire new)
   */
  async getToken(): Promise<string> {
    const now = Date.now();

    // Check cache validity (with 60s buffer for expiration)
    if (this.cache && this.cache.expiresOn - 60000 > now) {
      return this.cache.token;
    }

    try {
      const result = await this.cca.acquireTokenByClientCredential({
        scopes: ['https://graph.microsoft.com/.default'],
      });

      if (!result?.accessToken) {
        throw new Error('Failed to acquire Graph token');
      }

      const expiresOn = result.expiresOn
        ? new Date(result.expiresOn).getTime()
        : now + 55 * 60 * 1000; // Default 55 minutes

      this.cache = {
        token: result.accessToken,
        expiresOn,
      };

      return result.accessToken;
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      throw new Error(`Failed to acquire Graph token: ${message}`);
    }
  }

  /**
   * Get token with expiration info
   */
  async getTokenWithExpiry(): Promise<{ token: string; expiresOnISO: string }> {
    const token = await this.getToken();
    const expiresOn = this.cache?.expiresOn || Date.now();
    return {
      token,
      expiresOnISO: new Date(expiresOn).toISOString(),
    };
  }

  /**
   * Check if token is cached and valid
   */
  isTokenValid(): boolean {
    if (!this.cache) return false;
    const now = Date.now();
    return this.cache.expiresOn - 60000 > now;
  }

  /**
   * Clear cache (force refresh on next call)
   */
  clearCache(): void {
    this.cache = null;
  }
}

/**
 * PocketBase Token Validation (Backend)
 * Validates and uses user PocketBase tokens
 */
export class PocketBaseValidator {
  private pb: PocketBase;

  constructor(pbUrl?: string) {
    this.pb = new PocketBase(pbUrl || process.env.PB_DB!);
  }

  /**
   * Set user token and validate it
   */
  async validateUserToken(token: string): Promise<boolean> {
    try {
      this.pb.authStore.save(token, null);
      await this.pb.collection('Users').authRefresh();
      return true;
    } catch (error) {
      console.error('Token validation failed:', error instanceof Error ? error.message : error);
      return false;
    }
  }

  /**
   * Get user record from token
   */
  async getUserRecord(token: string): Promise<any> {
    try {
      this.pb.authStore.save(token, null);
      const record = this.pb.authStore.record || this.pb.authStore.model;
      return record;
    } catch (error) {
      console.error('Failed to get user record:', error);
      return null;
    }
  }

  /**
   * Get PocketBase instance
   */
  getPocketBase(): PocketBase {
    return this.pb;
  }
}

/**
 * Combined backend auth manager
 */
export class BackendAuth {
  graphTokenManager: GraphTokenManager;
  pbValidator: PocketBaseValidator;

  constructor(config: BackendAuthConfig, pbUrl?: string) {
    this.graphTokenManager = new GraphTokenManager(config);
    this.pbValidator = new PocketBaseValidator(pbUrl);
  }

  /**
   * Middleware to validate PocketBase token in requests
   * Usage: app.use('/*', (c, next) => backendAuth.validateTokenMiddleware(c, next))
   */
  async validateTokenMiddleware(c: any, next: any): Promise<any> {
    const token = c.req.header('Authorization')?.replace('Bearer ', '') ||
                  (await c.req.json().catch(() => ({})))?.pbToken;

    if (token) {
      const isValid = await this.pbValidator.validateUserToken(token);
      if (!isValid) {
        return c.json({ error: 'Invalid token' }, 401);
      }
    }

    return next();
  }
}
