import path from 'node:path';
import fs from 'node:fs';
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { config } from '../../config.js';
import { vendorBinExists } from '../../lib/frameworks/composer.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';

function vendorBin(root: string, bin: string): string {
  const suffix = process.platform === 'win32' ? '.bat' : '';
  return path.join(root, 'vendor', 'bin', `${bin}${suffix}`);
}

function parsePhpUnitSummary(output: string): Record<string, unknown> {
  // PHPUnit: "OK (12 tests, 34 assertions)" or "FAILURES!\nTests: 12, Assertions: 30, Failures: 2."
  const ok = output.match(/OK\s*\((\d+)\s+tests?,\s*(\d+)\s+assertions?\)/i);
  if (ok) {
    return { passed: true, tests: Number(ok[1]), assertions: Number(ok[2]), failures: 0, errors: 0 };
  }

  const summary = output.match(
    /Tests:\s*(\d+),\s*Assertions:\s*(\d+)(?:,\s*Errors:\s*(\d+))?(?:,\s*Failures:\s*(\d+))?(?:,\s*Skipped:\s*(\d+))?/i,
  );
  if (summary) {
    return {
      passed: !/FAILURES!|ERRORS!/i.test(output),
      tests: Number(summary[1]),
      assertions: Number(summary[2]),
      errors: Number(summary[3] ?? 0),
      failures: Number(summary[4] ?? 0),
      skipped: Number(summary[5] ?? 0),
    };
  }

  // Pest often prints "Tests:  12 passed (34 assertions)" or similar.
  const pest = output.match(/Tests:\s+(\d+)\s+passed/i);
  if (pest) {
    return { passed: !/failed|FAIL/i.test(output), tests: Number(pest[1]) };
  }

  return { passed: null, note: 'Could not parse a pass/fail summary from the runner output.' };
}

export function registerPhpTestTools(server: McpServer): void {
  server.registerTool(
    'run_phpunit',
    {
      title: 'Run PHPUnit',
      description:
        "Run the Laravel project's PHPUnit suite via vendor/bin/phpunit (or `php artisan test` as a fallback). Returns parsed pass/fail counts when possible.",
      inputSchema: {
        extraArgs: z
          .array(z.string())
          .optional()
          .describe('Extra CLI args, e.g. ["--filter", "UserTest"].'),
      },
    },
    async ({ extraArgs }) => {
      try {
        const laravel = requireLaravel();
        const args = extraArgs ?? [];
        let command: string;
        let cmdArgs: string[];

        if (vendorBinExists(laravel.root, 'phpunit')) {
          command = vendorBin(laravel.root, 'phpunit');
          cmdArgs = args;
        } else if (fs.existsSync(path.join(laravel.root, 'artisan'))) {
          command = 'php';
          cmdArgs = ['artisan', 'test', '--without-tty', ...args];
        } else {
          return errorResult(
            `No vendor/bin/phpunit found at ${laravel.root}. Run composer install (or run_install) first.`,
          );
        }

        const result = await runCommand(command, cmdArgs, {
          cwd: laravel.root,
          timeoutMs: config.scaffoldCommandTimeoutMs,
        });
        return jsonResult({ runner: 'phpunit', summary: parsePhpUnitSummary(result.output), ...result });
      } catch (error) {
        return errorResult(toErrorMessage(error));
      }
    },
  );

  server.registerTool(
    'run_pest',
    {
      title: 'Run Pest',
      description:
        "Run the Laravel project's Pest suite via vendor/bin/pest. Falls back to an error if Pest is not installed.",
      inputSchema: {
        extraArgs: z
          .array(z.string())
          .optional()
          .describe('Extra CLI args, e.g. ["--filter", "it creates a post"].'),
      },
    },
    async ({ extraArgs }) => {
      try {
        const laravel = requireLaravel();
        if (!vendorBinExists(laravel.root, 'pest')) {
          return errorResult(
            `No vendor/bin/pest found at ${laravel.root}. Install pestphp/pest (composer_require) or use run_phpunit instead.`,
          );
        }
        const result = await runCommand(vendorBin(laravel.root, 'pest'), extraArgs ?? [], {
          cwd: laravel.root,
          timeoutMs: config.scaffoldCommandTimeoutMs,
        });
        return jsonResult({ runner: 'pest', summary: parsePhpUnitSummary(result.output), ...result });
      } catch (error) {
        return errorResult(toErrorMessage(error));
      }
    },
  );
}
