/**
 * Token Manager
 *
 * Manages GitHub token persistence and resolution.
 */

import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { AuthConfigSchema, type AuthConfig, type AuthStatus } from './types.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Config directory name
 */
const CONFIG_DIR = 'emdash';

/**
 * Auth config file name
 */
const AUTH_FILE = 'auth.json';

/**
 * Token Manager
 *
 * Handles token storage, loading, and resolution with priority:
 * 1. Stored auth config (~/.config/emdash/auth.json)
 * 2. GITHUB_TOKEN environment variable
 * 3. GITHUB_PERSONAL_ACCESS_TOKEN environment variable
 */
export class TokenManager {
  private logger: Logger;
  private cachedConfig: AuthConfig | null = null;

  constructor(logger?: Logger) {
    this.logger = logger ?? createLogger({ name: 'token-manager' });
  }

  /**
   * Get the config directory path
   */
  getConfigDir(): string {
    const xdgConfig = process.env.XDG_CONFIG_HOME;
    if (xdgConfig) {
      return path.join(xdgConfig, CONFIG_DIR);
    }
    return path.join(os.homedir(), '.config', CONFIG_DIR);
  }

  /**
   * Get the auth file path
   */
  getAuthFile(): string {
    return path.join(this.getConfigDir(), AUTH_FILE);
  }

  /**
   * Load auth config from file
   */
  async loadConfig(): Promise<AuthConfig | null> {
    if (this.cachedConfig) {
      return this.cachedConfig;
    }

    try {
      const content = await fs.readFile(this.getAuthFile(), 'utf-8');
      const parsed = JSON.parse(content);
      const config = AuthConfigSchema.parse(parsed);
      this.cachedConfig = config;
      return config;
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
        this.logger.debug({ error }, 'Failed to load auth config');
      }
      return null;
    }
  }

  /**
   * Save auth config to file
   */
  async saveConfig(config: AuthConfig): Promise<void> {
    const configDir = this.getConfigDir();
    const authFile = this.getAuthFile();

    // Ensure directory exists
    await fs.mkdir(configDir, { recursive: true });

    // Write config with restricted permissions
    const content = JSON.stringify(config, null, 2);
    await fs.writeFile(authFile, content, { mode: 0o600 });

    this.cachedConfig = config;
    this.logger.info({ username: config.username }, 'Saved auth config');
  }

  /**
   * Delete auth config
   */
  async deleteConfig(): Promise<boolean> {
    try {
      await fs.unlink(this.getAuthFile());
      this.cachedConfig = null;
      this.logger.info('Deleted auth config');
      return true;
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        return false;
      }
      throw error;
    }
  }

  /**
   * Get GitHub token with priority resolution
   *
   * Priority:
   * 1. Stored auth config
   * 2. GITHUB_TOKEN env var
   * 3. GITHUB_PERSONAL_ACCESS_TOKEN env var
   */
  async getToken(): Promise<string | null> {
    // Try stored config first
    const config = await this.loadConfig();
    if (config?.access_token) {
      return config.access_token;
    }

    // Try environment variables
    const envToken = process.env.GITHUB_TOKEN || process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
    if (envToken) {
      return envToken;
    }

    return null;
  }

  /**
   * Check if authenticated
   */
  async isAuthenticated(): Promise<boolean> {
    const token = await this.getToken();
    return token !== null;
  }

  /**
   * Get authentication status
   */
  async getStatus(): Promise<AuthStatus> {
    // Check stored config first
    const config = await this.loadConfig();
    if (config?.access_token) {
      const scopes = config.scope ? config.scope.split(',').map((s) => s.trim()) : [];
      return {
        authenticated: true,
        source: 'emdash_auth',
        username: config.username,
        scopes,
      };
    }

    // Check environment variables
    const envToken = process.env.GITHUB_TOKEN || process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
    if (envToken) {
      return {
        authenticated: true,
        source: 'environment_variable',
        scopes: [], // Can't determine scopes from env var
      };
    }

    return {
      authenticated: false,
      source: null,
      scopes: [],
    };
  }

  /**
   * Clear cached config
   */
  clearCache(): void {
    this.cachedConfig = null;
  }

  /**
   * Resolve token placeholder in environment variables
   *
   * Replaces ${GITHUB_TOKEN} with actual token value
   */
  async resolveEnvToken(value: string): Promise<string> {
    if (value === '${GITHUB_TOKEN}' || value === '${GITHUB_PERSONAL_ACCESS_TOKEN}') {
      const token = await this.getToken();
      return token ?? '';
    }
    return value;
  }

  /**
   * Resolve all token placeholders in an env object
   */
  async resolveEnv(env: Record<string, string>): Promise<Record<string, string>> {
    const resolved: Record<string, string> = {};

    for (const [key, value] of Object.entries(env)) {
      resolved[key] = await this.resolveEnvToken(value);
    }

    return resolved;
  }
}

// Singleton instance
let tokenManagerInstance: TokenManager | null = null;

/**
 * Get the token manager singleton
 */
export function getTokenManager(): TokenManager {
  if (!tokenManagerInstance) {
    tokenManagerInstance = new TokenManager();
  }
  return tokenManagerInstance;
}

/**
 * Create a new token manager (for testing)
 */
export function createTokenManager(logger?: Logger): TokenManager {
  return new TokenManager(logger);
}

/**
 * Convenience function to get GitHub token
 */
export async function getGitHubToken(): Promise<string | null> {
  return getTokenManager().getToken();
}

/**
 * Convenience function to check auth status
 */
export async function isAuthenticated(): Promise<boolean> {
  return getTokenManager().isAuthenticated();
}

/**
 * Convenience function to get auth status
 */
export async function getAuthStatus(): Promise<AuthStatus> {
  return getTokenManager().getStatus();
}
