/**
 * Git Hook Installer
 *
 * Installs and manages git pre-commit and pre-push hooks for AIWG validation.
 */

import { readFile, writeFile, chmod, access, mkdir } from 'fs/promises';
import { existsSync } from 'fs';
import { resolve } from 'path';
import { constants } from 'fs';

export type HookType = 'pre-commit' | 'pre-push';

export interface HookInstallOptions {
  force?: boolean; // Overwrite existing hook
  append?: boolean; // Append to existing hook
  configPath?: string; // Path to .aiwgrc.json
}

/**
 * Git Hook Installer
 */
export class GitHookInstaller {
  private projectRoot: string;
  private hooksDir: string;

  constructor(projectRoot: string) {
    this.projectRoot = projectRoot;
    this.hooksDir = resolve(projectRoot, '.git', 'hooks');
  }

  /**
   * Install pre-commit hook
   */
  async installPreCommitHook(options: HookInstallOptions = {}): Promise<void> {
    const hookContent = this.generatePreCommitHook(options.configPath);
    await this.installHook('pre-commit', hookContent, options);
  }

  /**
   * Install pre-push hook
   */
  async installPrePushHook(options: HookInstallOptions = {}): Promise<void> {
    const hookContent = this.generatePrePushHook(options.configPath);
    await this.installHook('pre-push', hookContent, options);
  }

  /**
   * Uninstall hooks
   */
  async uninstallHooks(projectRoot: string): Promise<void> {
    const hooksToRemove: HookType[] = ['pre-commit', 'pre-push'];

    for (const hookType of hooksToRemove) {
      const hookPath = resolve(projectRoot, '.git', 'hooks', hookType);

      if (existsSync(hookPath)) {
        const content = await readFile(hookPath, 'utf-8');

        // Only remove if it's our hook
        if (content.includes('# AIWG')) {
          // Check if there's other content
          const lines = content.split('\n');
          const aiwgStart = lines.findIndex(l => l.includes('# AIWG'));
          const aiwgEnd = lines.findIndex((l, i) => i > aiwgStart && l.includes('# END AIWG'));

          if (aiwgStart >= 0 && aiwgEnd >= 0) {
            // Remove only AIWG section
            const beforeAiwg = lines.slice(0, aiwgStart);
            const afterAiwg = lines.slice(aiwgEnd + 1);

            // Check if the content before AIWG is just shebang (part of our hook)
            const beforeContent = beforeAiwg.filter(l => l.trim().length > 0);
            const isOnlyShebang = beforeContent.length <= 1 && beforeContent.every(l => l.startsWith('#!'));

            // Check if after AIWG is meaningful content
            const afterContent = afterAiwg.filter(l => l.trim().length > 0);

            if (isOnlyShebang && afterContent.length === 0) {
              // File only contains our hook (shebang + AIWG), remove entirely
              await this.removeFile(hookPath);
            } else {
              // There's other content, keep it
              const newContent = [...beforeAiwg, ...afterAiwg].join('\n');

              if (newContent.trim().length > 0) {
                await writeFile(hookPath, newContent, 'utf-8');
              } else {
                // File would be empty, remove it
                await this.removeFile(hookPath);
              }
            }
          } else {
            // Entire file is AIWG hook, remove it
            await this.removeFile(hookPath);
          }
        }
      }
    }
  }

  /**
   * Check if hook is installed
   */
  isInstalled(hookType: HookType): boolean {
    const hookPath = resolve(this.hooksDir, hookType);

    if (!existsSync(hookPath)) {
      return false;
    }

    try {
      const content = require('fs').readFileSync(hookPath, 'utf-8');
      return content.includes('# AIWG');
    } catch {
      return false;
    }
  }

  /**
   * Get hook content
   */
  async getHookContent(hookType: HookType): Promise<string | null> {
    const hookPath = resolve(this.hooksDir, hookType);

    if (!existsSync(hookPath)) {
      return null;
    }

    return await readFile(hookPath, 'utf-8');
  }

  /**
   * Verify git repository
   */
  isGitRepository(): boolean {
    return existsSync(resolve(this.projectRoot, '.git'));
  }

  // Private methods

  private async installHook(
    hookType: HookType,
    content: string,
    options: HookInstallOptions
  ): Promise<void> {
    // Verify git repository
    if (!this.isGitRepository()) {
      throw new Error('Not a git repository');
    }

    // Ensure hooks directory exists
    if (!existsSync(this.hooksDir)) {
      await mkdir(this.hooksDir, { recursive: true });
    }

    const hookPath = resolve(this.hooksDir, hookType);

    // Check if hook already exists
    if (existsSync(hookPath) && !options.force && !options.append) {
      throw new Error(`Hook ${hookType} already exists. Use --force to overwrite or --append to add to existing hook.`);
    }

    let finalContent = content;

    if (options.append && existsSync(hookPath)) {
      // Append to existing hook
      const existing = await readFile(hookPath, 'utf-8');

      // Check if AIWG section already exists
      if (existing.includes('# AIWG')) {
        throw new Error(`AIWG hook already exists in ${hookType}. Use --force to overwrite.`);
      }

      finalContent = `${existing}\n\n${content}`;
    }

    // Write hook file
    await writeFile(hookPath, finalContent, 'utf-8');

    // Make executable
    await chmod(hookPath, 0o755);
  }

  private generatePreCommitHook(configPath?: string): string {
    const configArg = configPath ? ` --config ${configPath}` : '';

    return `#!/bin/sh
# AIWG pre-commit hook
# Validates staged markdown and text files before commit

echo "Running AIWG validation..."

# Get staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(md|txt)$' || true)

if [ -z "$STAGED_FILES" ]; then
  echo "No markdown or text files to validate"
  exit 0
fi

# Run AIWG workflow
node tools/cli/aiwg.mjs workflow $STAGED_FILES${configArg}
RESULT=$?

if [ $RESULT -ne 0 ]; then
  echo ""
  echo "AIWG validation failed. Please fix issues before committing."
  echo "To bypass this check, use: git commit --no-verify"
  exit 1
fi

echo "AIWG validation passed"
exit 0

# END AIWG
`;
  }

  private generatePrePushHook(configPath?: string): string {
    const configArg = configPath ? ` --config ${configPath}` : '';

    return `#!/bin/sh
# AIWG pre-push hook
# Validates all markdown and text files before push

echo "Running AIWG validation before push..."

# Find all markdown and text files
FILES=$(find . -type f \\( -name "*.md" -o -name "*.txt" \\) -not -path "*/node_modules/*" -not -path "*/.git/*" || true)

if [ -z "$FILES" ]; then
  echo "No markdown or text files to validate"
  exit 0
fi

# Run AIWG workflow
node tools/cli/aiwg.mjs workflow $FILES${configArg}
RESULT=$?

if [ $RESULT -ne 0 ]; then
  echo ""
  echo "AIWG validation failed. Please fix issues before pushing."
  echo "To bypass this check, use: git push --no-verify"
  exit 1
fi

echo "AIWG validation passed"
exit 0

# END AIWG
`;
  }

  private async removeFile(path: string): Promise<void> {
    const fs = await import('fs/promises');
    await fs.unlink(path);
  }

  /**
   * List all installed hooks
   */
  async listInstalledHooks(): Promise<HookType[]> {
    const installed: HookType[] = [];
    const hookTypes: HookType[] = ['pre-commit', 'pre-push'];

    for (const hookType of hookTypes) {
      if (this.isInstalled(hookType)) {
        installed.push(hookType);
      }
    }

    return installed;
  }

  /**
   * Validate hook installation
   */
  async validateHook(hookType: HookType): Promise<{ valid: boolean; error?: string }> {
    const hookPath = resolve(this.hooksDir, hookType);

    if (!existsSync(hookPath)) {
      return { valid: false, error: 'Hook file does not exist' };
    }

    try {
      // Check if executable
      await access(hookPath, constants.X_OK);
    } catch {
      return { valid: false, error: 'Hook file is not executable' };
    }

    // Check if contains AIWG marker
    const content = await readFile(hookPath, 'utf-8');
    if (!content.includes('# AIWG')) {
      return { valid: false, error: 'Hook does not contain AIWG marker' };
    }

    return { valid: true };
  }
}
