import { evalite } from "evalite";
import { Levenshtein } from "autoevals";
import { AIQueryService } from "~/v3/services/aiQueryService.server";
import { runsSchema } from "~/v3/querySchemas";
import dotenv from "dotenv";
import { wrapAISDKModel } from "evalite/ai-sdk";
import { openai } from "@ai-sdk/openai";

dotenv.config({ path: "../../.env" });

// Helper to normalize queries for comparison
function normalizeQuery(query: string): string {
  return query
    .replace(/\s+/g, " ")
    .replace(/\(\s+/g, "(")
    .replace(/\s+\)/g, ")")
    .trim()
    .toLowerCase();
}

// Type for parsed query results
interface ParsedQueryResult {
  success: boolean;
  query?: string;
  error?: string;
}

// Custom scorer that checks if the generated query is semantically similar
// and also syntactically valid
const QuerySimilarity = {
  name: "QuerySimilarity",
  scorer: async ({
    input,
    output,
    expected,
  }: {
    input: string;
    output: string;
    expected?: string;
  }) => {
    if (!expected) {
      return 0;
    }

    // Parse the output to extract the query
    const outputParsed = JSON.parse(output) as ParsedQueryResult;
    const expectedParsed = JSON.parse(expected) as ParsedQueryResult;

    // Check success status first
    if (outputParsed.success !== expectedParsed.success) {
      return 0;
    }

    // If both failed, check if error messages are similar
    if (!outputParsed.success && !expectedParsed.success) {
      // Give partial credit for correctly identifying an error case
      return 0.5;
    }

    // If both succeeded, compare the queries
    if (outputParsed.success && expectedParsed.success) {
      const normalizedOutput = normalizeQuery(outputParsed.query ?? "");
      const normalizedExpected = normalizeQuery(expectedParsed.query ?? "");

      // Key patterns to check
      const patterns = [
        // Table name
        /from\s+runs/i,
        // Status filter patterns
        /status\s*=\s*'[^']+'/i,
        /status\s+in\s*\([^)]+\)/i,
        // Time patterns
        /interval\s+\d+\s+(day|hour|minute|week|month)/i,
        /triggered_at\s*>/i,
        // Aggregation patterns
        /count\(\)/i,
        /sum\(/i,
        /avg\(/i,
        /group\s+by/i,
        // Ordering
        /order\s+by/i,
        // Limit
        /limit\s+\d+/i,
      ];

      let matchScore = 0;
      let totalPatterns = 0;

      for (const pattern of patterns) {
        const outputMatch = pattern.test(normalizedOutput);
        const expectedMatch = pattern.test(normalizedExpected);

        if (expectedMatch) {
          totalPatterns++;
          if (outputMatch) {
            matchScore++;
          }
        }
      }

      // Base score from pattern matching
      const patternScore = totalPatterns > 0 ? matchScore / totalPatterns : 0.5;

      // Use Levenshtein for overall similarity
      const levenshteinResult = await Levenshtein({
        output: normalizedOutput,
        expected: normalizedExpected,
      });
      const levenshteinScore = levenshteinResult?.score ?? 0;

      // Weighted combination
      return 0.6 * patternScore + 0.4 * levenshteinScore;
    }

    return 0;
  },
};

evalite("AI Query Generator", {
  data: async () => {
    return [
      // Basic SELECT queries
      {
        input: "Show me all runs",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
LIMIT 100`,
        }),
      },
      {
        input: "Get the 10 most recent runs",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
ORDER BY triggered_at DESC
LIMIT 10`,
        }),
      },

      // Status filtering
      {
        input: "Show failed runs",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE status = 'Failed'
LIMIT 100`,
        }),
      },
      {
        input: "Get all completed runs",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE status = 'Completed'
LIMIT 100`,
        }),
      },
      {
        input: "Find runs that crashed or timed out",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE status IN ('Crashed', 'Timed out')
LIMIT 100`,
        }),
      },

      // Time-based filtering
      {
        input: "Runs from the last 7 days",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE triggered_at > now() - INTERVAL 7 DAY
LIMIT 100`,
        }),
      },
      {
        input: "Show runs from the past hour",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE triggered_at > now() - INTERVAL 1 HOUR
LIMIT 100`,
        }),
      },
      {
        input: "Failed runs in the last 24 hours",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE status = 'Failed'
  AND triggered_at > now() - INTERVAL 1 DAY
ORDER BY triggered_at DESC
LIMIT 100`,
        }),
      },

      // Aggregations
      {
        input: "Count of runs by status",
        expected: JSON.stringify({
          success: true,
          query: `SELECT status, count() AS count
FROM runs
GROUP BY status
ORDER BY count DESC`,
        }),
      },
      {
        input: "How many runs per task?",
        expected: JSON.stringify({
          success: true,
          query: `SELECT task_identifier, count() AS run_count
FROM runs
GROUP BY task_identifier
ORDER BY run_count DESC
LIMIT 100`,
        }),
      },
      {
        input: "Average execution duration by task",
        expected: JSON.stringify({
          success: true,
          query: `SELECT task_identifier, avg(execution_duration) AS avg_duration
FROM runs
GROUP BY task_identifier
ORDER BY avg_duration DESC
LIMIT 100`,
        }),
      },
      {
        input: "Total cost by task in the last 30 days",
        expected: JSON.stringify({
          success: true,
          query: `SELECT task_identifier, sum(total_cost) AS total_cost
FROM runs
WHERE triggered_at > now() - INTERVAL 30 DAY
GROUP BY task_identifier
ORDER BY total_cost DESC
LIMIT 100`,
        }),
      },

      // Complex queries
      {
        input: "Top 10 most expensive failed runs from last week",
        expected: JSON.stringify({
          success: true,
          query: `SELECT run_id, task_identifier, status, total_cost, triggered_at
FROM runs
WHERE status = 'Failed'
  AND triggered_at > now() - INTERVAL 7 DAY
ORDER BY total_cost DESC
LIMIT 10`,
        }),
      },
      {
        input: "Runs using large machines that took more than 5 minutes",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE machine IN ('large-1x', 'large-2x')
  AND usage_duration > 300000
LIMIT 100`,
        }),
      },
      {
        input: "Show p95 execution duration by task for completed runs",
        expected: JSON.stringify({
          success: true,
          query: `SELECT task_identifier, quantile(0.95)(execution_duration) AS p95_duration
FROM runs
WHERE status = 'Completed'
  AND execution_duration IS NOT NULL
GROUP BY task_identifier
ORDER BY p95_duration DESC
LIMIT 100`,
        }),
      },

      // Specific columns
      {
        input: "Just show run IDs and their statuses",
        expected: JSON.stringify({
          success: true,
          query: `SELECT run_id, status
FROM runs
LIMIT 100`,
        }),
      },
      {
        input: "Get run_id, task, status and cost for recent runs",
        expected: JSON.stringify({
          success: true,
          query: `SELECT run_id, task_identifier, status, total_cost
FROM runs
ORDER BY triggered_at DESC
LIMIT 100`,
        }),
      },

      // Root runs
      {
        input: "Show only root runs (not child runs)",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE is_root_run = 1
LIMIT 100`,
        }),
      },

      // Queue filtering
      {
        input: "Runs in the shared queue",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE queue LIKE '%shared%'
LIMIT 100`,
        }),
      },

      // Tags
      {
        input: "Find runs with tag 'important'",
        expected: JSON.stringify({
          success: true,
          query: `SELECT *
FROM runs
WHERE has(tags, 'important')
LIMIT 100`,
        }),
      },

      // Error cases
      {
        input: "Do something",
        expected: JSON.stringify({
          success: false,
          error: "Please be more specific about what data you want to query",
        }),
      },
      {
        input: "Show me the weather",
        expected: JSON.stringify({
          success: false,
          error: "I can only generate queries for task run data",
        }),
      },
    ];
  },
  task: async (input) => {
    const service = new AIQueryService([runsSchema], wrapAISDKModel(openai("gpt-4o-mini")));

    const result = await service.call(input);
    return JSON.stringify(result);
  },
  scorers: [QuerySimilarity, Levenshtein],
});
