'use node';

import { getAuthUserId } from '@convex-dev/auth/server';
import { ConvexError, v } from 'convex/values';

import type { Doc, Id } from './_generated/dataModel';
import type { ActionCtx } from './_generated/server';
import { internal } from './_generated/api';
import { action } from './_generated/server';
import { reviewUpstreamCompatibility } from './openaiClient';
import { decryptSecret } from './secretCrypto';

const getRequiredUserId = async (ctx: ActionCtx): Promise<Id<'users'>> => {
  const userId = await getAuthUserId(ctx);
  if (!userId) throw new ConvexError('Not authenticated.');
  return userId;
};

export const reviewLatestUpstreamChanges = action({
  args: { spoonId: v.id('spoons') },
  handler: async (
    ctx,
    { spoonId },
  ): Promise<{
    reviewId: Id<'aiReviews'>;
    risk: 'low' | 'medium' | 'high';
    recommendedAction:
      | 'sync'
      | 'open_review_pr'
      | 'manual_review'
      | 'do_not_sync';
  }> => {
    const ownerId = await getRequiredUserId(ctx);
    const spoon: Doc<'spoons'> = await ctx.runQuery(
      internal.spoons.getOwnedForAction,
      {
        spoonId,
        ownerId,
      },
    );
    const [state, settings, upstreamCommits, forkCommits]: [
      Doc<'spoonRepositoryStates'> | null,
      Doc<'spoonSettings'> | null,
      Doc<'spoonCommits'>[],
      Doc<'spoonCommits'>[],
    ] = await Promise.all([
      ctx.runQuery(internal.spoonState.getInternal, { spoonId, ownerId }),
      ctx.runQuery(internal.spoonSettings.getInternal, { spoonId, ownerId }),
      ctx.runQuery(internal.spoonCommits.listInternal, {
        spoonId,
        ownerId,
        side: 'upstream',
        limit: 80,
      }),
      ctx.runQuery(internal.spoonCommits.listInternal, {
        spoonId,
        ownerId,
        side: 'fork',
        limit: 80,
      }),
    ]);
    const aiSettings: Doc<'userAiSettings'> | null = await ctx.runQuery(
      internal.aiSettings.getForUserInternal,
      { userId: ownerId },
    );
    if (!aiSettings?.encryptedApiKey) {
      throw new ConvexError(
        'Add your OpenAI API key in Settings before running AI review.',
      );
    }
    const model = aiSettings.model;
    const syncRunId: Id<'syncRuns'> = await ctx.runMutation(
      internal.syncRuns.createInternal,
      {
        spoonId,
        ownerId,
        kind: 'ai_review',
        status: 'running',
        summary: 'Reviewing upstream changes with OpenAI.',
      },
    );
    const reviewId: Id<'aiReviews'> = await ctx.runMutation(
      internal.aiReviews.createInternal,
      {
        spoonId,
        ownerId,
        syncRunId,
        model,
        status: 'running',
        reviewType: 'upstream_update',
        inputSummary: `${upstreamCommits.length} upstream commit(s), ${forkCommits.length} fork-only commit(s).`,
      },
    );
    try {
      if (upstreamCommits.length === 0) {
        await ctx.runMutation(internal.aiReviews.completeInternal, {
          reviewId,
          outputSummary: 'The fork is already up to date with upstream.',
          risk: 'low',
          compatible: true,
          requiresHumanReview: false,
          recommendedAction: 'sync',
          potentialConflicts: [],
          importantFiles: [],
          reasoningSummary:
            'No upstream-only commits are cached for this Spoon, so there is nothing to review.',
        });
        await Promise.all([
          ctx.runMutation(internal.spoons.patchSyncFields, {
            spoonId,
            lastAiReviewId: reviewId,
          }),
          ctx.runMutation(internal.syncRuns.patchInternal, {
            syncRunId,
            status: 'clean',
            aiAssessment: 'No upstream-only commits are waiting.',
          }),
        ]);
        return { reviewId, risk: 'low', recommendedAction: 'sync' };
      }

      const review = await reviewUpstreamCompatibility(
        {
          spoonName: spoon.name,
          upstreamFullName:
            state?.upstreamFullName ??
            `${spoon.upstreamOwner}/${spoon.upstreamRepo}`,
          forkFullName:
            state?.forkFullName ??
            `${spoon.forkOwner ?? 'unknown'}/${spoon.forkRepo ?? 'unknown'}`,
          status: state?.status ?? spoon.syncStatus ?? 'unknown',
          upstreamAheadBy: state?.upstreamAheadBy ?? spoon.upstreamAheadBy ?? 0,
          forkAheadBy: state?.forkAheadBy ?? spoon.forkAheadBy ?? 0,
          upstreamCommits: upstreamCommits.map((commit) => ({
            sha: commit.sha,
            message: commit.message,
            authorName: commit.authorName,
            committedAt: commit.committedAt,
          })),
          forkCommits: forkCommits.map((commit) => ({
            sha: commit.sha,
            message: commit.message,
            authorName: commit.authorName,
            committedAt: commit.committedAt,
          })),
          importantFilePatterns: settings?.importantFilePatterns,
          ignoredFilePatterns: settings?.ignoredFilePatterns,
        },
        {
          apiKey: decryptSecret(aiSettings.encryptedApiKey),
          model,
          reasoningEffort: aiSettings.reasoningEffort,
        },
      );
      await ctx.runMutation(internal.aiReviews.completeInternal, {
        reviewId,
        outputSummary: review.summary,
        risk: review.risk,
        compatible: review.compatible,
        requiresHumanReview: review.requiresHumanReview,
        recommendedAction: review.recommendedAction,
        potentialConflicts: review.potentialConflicts,
        importantFiles: review.importantFiles,
        reasoningSummary: review.reasoningSummary,
      });
      await Promise.all([
        ctx.runMutation(internal.spoons.patchSyncFields, {
          spoonId,
          lastAiReviewId: reviewId,
        }),
        ctx.runMutation(internal.syncRuns.patchInternal, {
          syncRunId,
          status:
            review.compatible && review.risk === 'low'
              ? 'clean'
              : 'needs_review',
          aiAssessment: review.summary,
        }),
      ]);
      return {
        reviewId,
        risk: review.risk,
        recommendedAction: review.recommendedAction,
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      await Promise.all([
        ctx.runMutation(internal.aiReviews.failInternal, {
          reviewId,
          error: message,
        }),
        ctx.runMutation(internal.syncRuns.patchInternal, {
          syncRunId,
          status: 'failed',
          error: message,
        }),
      ]);
      throw new ConvexError(message);
    }
  },
});
