import { eq, desc } from "drizzle-orm";
import { getSkillRegistryDb } from "./skill-registry-db.js";
import { personalRatings, agentSkills } from "./skill-registry-schema.js";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

type RateOpts = {
  skillId: string;
  versionId?: string | null;
  stars: number;
  note?: string | null;
};

type PersonalRatingRow = typeof personalRatings.$inferSelect;

// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------

/**
 * Skill rating service factory.
 * Manages personal ratings and usage tracking in the skill registry libSQL DB.
 */
export function skillRatingService() {
  return {
    /**
     * Record a personal rating (1-5 stars) for a skill.
     * Always appends — never upserts — so rating history is preserved.
     */
    async rate(opts: RateOpts): Promise<void> {
      if (opts.stars < 1 || opts.stars > 5) {
        throw new RangeError(`stars must be between 1 and 5, got ${opts.stars}`);
      }
      const db = await getSkillRegistryDb();
      const now = Date.now();
      await db.insert(personalRatings).values({
        id: crypto.randomUUID(),
        skillId: opts.skillId,
        versionId: opts.versionId ?? null,
        stars: opts.stars,
        note: opts.note ?? null,
        createdAt: now,
        updatedAt: now,
      });
    },

    /**
     * Get all personal ratings for a skill, ordered by createdAt descending (newest first).
     */
    async getRatings(skillId: string): Promise<PersonalRatingRow[]> {
      const db = await getSkillRegistryDb();
      return db
        .select()
        .from(personalRatings)
        .where(eq(personalRatings.skillId, skillId))
        .orderBy(desc(personalRatings.createdAt));
    },

    /**
     * Record a heartbeat run completion for all skills installed by an agent.
     * Increments task_count, updates running average cost, and sets last_used_at.
     * Safe to call when agent has no skills (no-op).
     *
     * @param agentId - the agent that just completed a successful run
     * @param costUsd - total cost of the run in USD, or null if unknown
     */
    async recordUsageForAgent(agentId: string, costUsd: number | null): Promise<void> {
      const db = await getSkillRegistryDb();
      const client = db.$client as import("@libsql/client").Client;
      const now = Date.now();

      if (costUsd !== null) {
        // Atomic update with running average calculation
        await client.execute({
          sql: `UPDATE agent_skills
                SET task_count = task_count + 1,
                    avg_cost_usd = CASE
                      WHEN task_count = 0 THEN ?
                      ELSE (COALESCE(avg_cost_usd, 0) * task_count + ?) / (task_count + 1)
                    END,
                    last_used_at = ?
                WHERE agent_id = ?`,
          args: [costUsd, costUsd, now, agentId],
        });
      } else {
        // Skip avg_cost_usd update when cost is unknown
        await client.execute({
          sql: `UPDATE agent_skills
                SET task_count = task_count + 1,
                    last_used_at = ?
                WHERE agent_id = ?`,
          args: [now, agentId],
        });
      }
    },
  };
}
