/**
 * Datacollect 의존성(Python 패키지) 자동 설치/검증 모듈.
 *
 * 의도: Astra extension 만 깔고 끝나면 `/youtube`, `/research` 같은 datacollect
 * 슬래시 명령은 bridge 의 Python 의존성 (`yt-dlp`, `youtube-transcript-api`) 이
 * 없어서 실패한다. 사용자가 그걸 매번 수동으로 깔아야 하는 friction 을 없애려고
 * VS Code 명령 + notification 으로 "한 클릭 설치" 경로 제공.
 *
 * 범위:
 *   - 우리가 자동으로 깔 수 있는 것 → Python 패키지만. (bridge 자체는 별도
 *     Datacollector_MAC 프로젝트라 사용자가 그쪽에서 `npm install + npm run bridge`
 *     해야 함 — 우리는 안내만)
 *   - 시스템 Python 자체 설치는 OS 정책상 자동화 위험 → python.org 링크 안내만.
 */
import * as vscode from 'vscode';
import { spawn } from 'child_process';
import { logInfo, logError } from '../../utils';

/** Datacollect 슬래시 명령들이 의존하는 Python 패키지. */
export const REQUIRED_PY_PACKAGES = ['yt-dlp', 'youtube-transcript-api'] as const;

export interface PythonProbe {
    /** Detected python executable (`python3` / `python` / `py`) or null. */
    pythonCmd: string | null;
    /** Python --version string (or null if not found). */
    version: string | null;
    /** Importable packages we required. */
    installedPackages: Set<string>;
    /** Required packages that aren't currently importable. */
    missingPackages: string[];
}

/**
 * Python 가용 여부 + 필수 패키지 import 여부를 한 번에 진단.
 * 어느 단계에서든 실패해도 throw 하지 않고 `pythonCmd: null` 또는
 * `missingPackages` 채워서 돌려준다 — 호출자가 UI 분기 하기 쉽도록.
 */
export async function probePythonEnv(): Promise<PythonProbe> {
    const result: PythonProbe = {
        pythonCmd: null,
        version: null,
        installedPackages: new Set<string>(),
        missingPackages: [...REQUIRED_PY_PACKAGES],
    };

    // Windows 는 `python` / `py` 가 보통 우선, 그 외엔 `python3` 가 안전.
    const candidates = process.platform === 'win32'
        ? ['python', 'py', 'python3']
        : ['python3', 'python'];

    for (const cmd of candidates) {
        const ver = await _capture(cmd, ['--version'], 5_000);
        if (ver.exitCode === 0 && /Python\s+3\./i.test(ver.stdout + ver.stderr)) {
            result.pythonCmd = cmd;
            result.version = (ver.stdout || ver.stderr).trim();
            break;
        }
    }
    if (!result.pythonCmd) return result;

    // 패키지 import 시도 — 패키지명과 import 명이 다른 케이스(`yt-dlp` → `yt_dlp`)는
    // 매핑 테이블로 처리.
    const importNameOf: Record<string, string> = {
        'yt-dlp': 'yt_dlp',
        'youtube-transcript-api': 'youtube_transcript_api',
    };
    const missing: string[] = [];
    for (const pkg of REQUIRED_PY_PACKAGES) {
        const importName = importNameOf[pkg] ?? pkg.replace(/-/g, '_');
        const probe = await _capture(result.pythonCmd, ['-c', `import ${importName}`], 8_000);
        if (probe.exitCode === 0) {
            result.installedPackages.add(pkg);
        } else {
            missing.push(pkg);
        }
    }
    result.missingPackages = missing;
    return result;
}

/**
 * pip install 실행. macOS homebrew Python (PEP 668) 처럼 시스템 Python 보호가
 * 켜져 있는 환경을 자동 감지해서 `--user --break-system-packages` 조합 시도.
 * 사용자 site-packages 로 가서 시스템 Python 은 안 건드리는 안전한 형태.
 *
 * VS Code OutputChannel 에 진행상황 streaming. 성공/실패 boolean 반환.
 */
export async function installMissingPackages(
    pythonCmd: string,
    packages: string[],
    output: vscode.OutputChannel,
): Promise<boolean> {
    if (packages.length === 0) return true;
    output.appendLine(`\n[Astra Setup] pip install 시작: ${packages.join(', ')}`);
    output.appendLine(`[Astra Setup] Python: ${pythonCmd}`);

    // 1차 시도: 표준 user-install. 정상 환경에선 충분.
    const firstAttempt = await _streamCommand(pythonCmd, ['-m', 'pip', 'install', '--user', ...packages], output, 5 * 60_000);
    if (firstAttempt) {
        output.appendLine(`[Astra Setup] ✅ 설치 성공 (--user)`);
        return true;
    }

    // PEP 668 (homebrew / debian 등) 보호 환경 자동 폴백. --break-system-packages
    // 라는 이름이 무서워 보이지만, `--user` 와 함께 쓰면 user site 로 가서 시스템
    // Python 을 건드리지 않는다. 일반 패키지(yt-dlp 등) 설치엔 안전.
    output.appendLine(`[Astra Setup] ⚠️ 1차 실패. PEP 668 환경으로 추정 → --break-system-packages 폴백 시도.`);
    const secondAttempt = await _streamCommand(
        pythonCmd,
        ['-m', 'pip', 'install', '--user', '--break-system-packages', ...packages],
        output,
        5 * 60_000,
    );
    if (secondAttempt) {
        output.appendLine(`[Astra Setup] ✅ 설치 성공 (--user --break-system-packages)`);
        return true;
    }

    output.appendLine(`[Astra Setup] ❌ 두 차례 시도 모두 실패. 수동 설치가 필요할 수 있습니다.`);
    return false;
}

/**
 * VS Code 명령 핸들러 — `Astra: Setup Datacollect Dependencies` 의 본체.
 * 사용자가 명령 팔레트에서 직접 호출하거나 notification 의 "Install Now" 버튼이
 * 호출. 모든 진행상황은 OutputChannel + window message 로 통보.
 */
export async function runDatacollectSetup(): Promise<void> {
    const output = vscode.window.createOutputChannel('Astra Setup');
    output.show(true);
    output.appendLine('🔧 Astra Datacollect 의존성 점검 시작...');
    output.appendLine('');

    const probe = await probePythonEnv();
    if (!probe.pythonCmd) {
        output.appendLine('❌ Python 3 을 찾지 못했습니다.');
        output.appendLine('   - macOS: brew install python3  또는  https://www.python.org 에서 설치');
        output.appendLine('   - Windows: https://www.python.org 에서 설치 (Add Python to PATH 체크)');
        output.appendLine('   - Linux: 패키지 매니저로 설치 (apt install python3 / yum install python3 …)');
        vscode.window.showErrorMessage(
            'Astra Setup: Python 3 이 PATH 에 없습니다. python.org 에서 설치 후 다시 시도하세요.',
            'python.org 열기',
        ).then((pick) => {
            if (pick === 'python.org 열기') {
                vscode.env.openExternal(vscode.Uri.parse('https://www.python.org/downloads/'));
            }
        });
        return;
    }
    output.appendLine(`✅ Python 감지: ${probe.pythonCmd} (${probe.version})`);
    output.appendLine(`   설치된 패키지: ${Array.from(probe.installedPackages).join(', ') || '(없음)'}`);
    output.appendLine(`   누락된 패키지: ${probe.missingPackages.join(', ') || '(없음)'}`);
    output.appendLine('');

    if (probe.missingPackages.length === 0) {
        output.appendLine('🎉 필수 패키지가 모두 설치돼 있습니다. 아무 작업도 필요 없습니다.');
        vscode.window.showInformationMessage('Astra Setup: 모든 Python 의존성이 이미 설치돼 있습니다.');
        return;
    }

    const ok = await installMissingPackages(probe.pythonCmd, probe.missingPackages, output);
    if (ok) {
        // 설치 후 재검증 — 정말 import 되는지 한 번 더 확인.
        const after = await probePythonEnv();
        if (after.missingPackages.length === 0) {
            output.appendLine('\n✅ 설치 후 import 검증 통과. Datacollect 슬래시 명령을 바로 쓸 수 있습니다.');
            vscode.window.showInformationMessage(
                `Astra Setup 완료: ${probe.missingPackages.join(', ')} 설치됨. /youtube 등 다시 시도해 보세요.`,
            );
        } else {
            output.appendLine(`\n⚠️ pip 은 성공으로 끝났지만 import 검증에서 여전히 ${after.missingPackages.join(', ')} 가 안 보입니다.`);
            output.appendLine('   Python 인터프리터가 여러 개 있거나 venv 가 활성화돼 있을 수 있어요. 터미널에서 직접 확인해 보세요:');
            output.appendLine(`     ${probe.pythonCmd} -c "import yt_dlp; print(yt_dlp.__file__)"`);
            vscode.window.showWarningMessage('Astra Setup: 설치는 완료됐지만 import 검증 실패. Output 채널 확인.');
        }
    } else {
        vscode.window.showErrorMessage(
            'Astra Setup: pip install 실패. Output 채널의 로그를 확인하세요.',
            'Output 열기',
        ).then((pick) => {
            if (pick === 'Output 열기') output.show(true);
        });
    }
}

/**
 * 패키지 미설치 사용자에게 보여주는 친절한 notification.
 * slashRouter 의 에러 catch 에서 "필수 패키지가 없습니다" 패턴을 잡으면 이걸
 * 호출 → 사용자가 "Install Now" 한 번 누르면 위 setup 명령이 돌아간다.
 *
 * Idempotent — 사용자가 dismiss 하거나 무시해도 다음 에러에서 다시 뜸.
 */
export async function offerInstallNotification(missingHint: string): Promise<void> {
    const pick = await vscode.window.showWarningMessage(
        `Astra: Datacollect 의 Python 의존성이 누락돼 있습니다 (${missingHint}). 지금 자동 설치할까요?`,
        { modal: false },
        'Install Now',
        '나중에',
    );
    if (pick === 'Install Now') {
        await vscode.commands.executeCommand('g1nation.setupDatacollect');
    }
}

// ─── Internal helpers ───────────────────────────────────────────────────────

function _capture(cmd: string, args: string[], timeoutMs: number): Promise<{ exitCode: number; stdout: string; stderr: string }> {
    return new Promise((resolve) => {
        let stdout = '';
        let stderr = '';
        let done = false;
        const finish = (exitCode: number) => {
            if (done) return;
            done = true;
            resolve({ exitCode, stdout, stderr });
        };
        try {
            const proc = spawn(cmd, args, { shell: false, windowsHide: true });
            const timer = setTimeout(() => {
                try { proc.kill('SIGKILL'); } catch { /* noop */ }
                finish(-1);
            }, timeoutMs);
            proc.stdout?.on('data', (b) => { stdout += b.toString(); });
            proc.stderr?.on('data', (b) => { stderr += b.toString(); });
            proc.on('error', () => { clearTimeout(timer); finish(-2); });
            proc.on('close', (code) => { clearTimeout(timer); finish(code ?? -3); });
        } catch {
            finish(-4);
        }
    });
}

function _streamCommand(cmd: string, args: string[], output: vscode.OutputChannel, timeoutMs: number): Promise<boolean> {
    return new Promise((resolve) => {
        output.appendLine(`$ ${cmd} ${args.join(' ')}`);
        let done = false;
        const finish = (ok: boolean) => {
            if (done) return;
            done = true;
            resolve(ok);
        };
        try {
            const proc = spawn(cmd, args, { shell: false, windowsHide: true });
            const timer = setTimeout(() => {
                output.appendLine(`[Astra Setup] ⏱️ ${timeoutMs / 1000}s 초과. 프로세스를 종료합니다.`);
                try { proc.kill('SIGKILL'); } catch { /* noop */ }
                finish(false);
            }, timeoutMs);
            proc.stdout?.on('data', (b) => output.append(b.toString()));
            proc.stderr?.on('data', (b) => output.append(b.toString()));
            proc.on('error', (e) => {
                clearTimeout(timer);
                logError('[datacollectSetup] spawn error', e);
                output.appendLine(`[Astra Setup] spawn 오류: ${e.message}`);
                finish(false);
            });
            proc.on('close', (code) => {
                clearTimeout(timer);
                logInfo(`[datacollectSetup] ${cmd} exited with code=${code}`);
                finish(code === 0);
            });
        } catch (e: any) {
            output.appendLine(`[Astra Setup] 실행 실패: ${e?.message ?? String(e)}`);
            finish(false);
        }
    });
}
