import { Injectable, ServiceUnavailableException } from '@nestjs/common';
import { execFile } from 'node:child_process';
import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { promisify } from 'node:util';

const execFileAsync = promisify(execFile);

export interface OfficeCliResult {
  success: boolean;
  data?: unknown;
  error?: string;
}

/**
 * Thin wrapper around the OfficeCli binary
 * (https://github.com/iOfficeAI/OfficeCli) used by the AI chat to
 * analyze uploaded Office documents (.xlsx / .docx / .pptx) on demand.
 * Arguments are passed as an argv array (no shell), with a hard timeout
 * and a generous output cap.
 */
@Injectable()
export class OfficeCliService {
  private resolvedBinary: string | null = null;

  async run(
    args: string[],
    options: { timeoutMs?: number; maxBuffer?: number } = {},
  ): Promise<OfficeCliResult> {
    const binary = await this.resolveBinary();
    try {
      const { stdout } = await execFileAsync(binary, args, {
        timeout: options.timeoutMs ?? 60_000,
        maxBuffer: options.maxBuffer ?? 64 * 1024 * 1024,
      });
      try {
        const parsed: unknown = JSON.parse(stdout);
        if (parsed && typeof parsed === 'object' && 'success' in parsed) {
          return parsed as OfficeCliResult;
        }
        return { success: true, data: parsed };
      } catch {
        return { success: false, error: 'OfficeCli 输出解析失败' };
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return { success: false, error: `OfficeCli 执行失败：${message.slice(0, 200)}` };
    }
  }

  async view(
    filePath: string,
    mode: 'stats' | 'outline' | 'text' | 'issues',
    extra: string[] = [],
  ): Promise<OfficeCliResult> {
    return this.run(['view', filePath, mode, '--json', ...extra]);
  }

  async get(filePath: string, path: string, depth?: number): Promise<OfficeCliResult> {
    return this.run([
      'get',
      filePath,
      path,
      '--json',
      ...(depth === undefined ? [] : ['--depth', String(depth)]),
    ]);
  }

  async query(filePath: string, selector: string): Promise<OfficeCliResult> {
    return this.run(['query', filePath, selector, '--json']);
  }

  private async resolveBinary(): Promise<string> {
    if (this.resolvedBinary) return this.resolvedBinary;
    const candidates = [process.env.OFFICECLI_BIN, this.bundledBinary()].filter(
      (value): value is string => Boolean(value),
    );
    for (const candidate of candidates) {
      try {
        await execFileAsync(candidate, ['--version'], { timeout: 5000 });
        this.resolvedBinary = candidate;
        return candidate;
      } catch {
        // try next candidate
      }
    }
    throw new ServiceUnavailableException(
      'OfficeCli 未安装：请运行 npm install（@officecli/officecli），或通过 OFFICECLI_BIN 指定二进制路径',
    );
  }

  /**
   * Prefer the `@officecli/officecli` npm package (binary fetched by its
   * postinstall) so a fresh machine only needs `npm install`.
   */
  private bundledBinary(): string | null {
    try {
      const mainPath = require.resolve('@officecli/officecli');
      const candidate = join(dirname(mainPath), '..', 'officecli.js');
      if (existsSync(candidate)) return candidate;
    } catch {
      // package not installed — fall through
    }
    for (const base of [process.cwd(), join(__dirname, '..', '..')]) {
      const candidate = join(base, 'node_modules', '@officecli', 'officecli', 'officecli.js');
      try {
        if (existsSync(candidate)) return candidate;
      } catch {
        // ignore
      }
    }
    return null;
  }
}
