import { ConvexError } from 'convex/values';
import OpenAI from 'openai';

type ReviewRisk = 'low' | 'medium' | 'high';
type ReviewAction = 'sync' | 'open_review_pr' | 'manual_review' | 'do_not_sync';

export type AiCompatibilityReview = {
  summary: string;
  risk: ReviewRisk;
  compatible: boolean;
  requiresHumanReview: boolean;
  recommendedAction: ReviewAction;
  potentialConflicts: string[];
  importantFiles: string[];
  reasoningSummary: string;
};

export type ReviewInput = {
  spoonName: string;
  upstreamFullName: string;
  forkFullName: string;
  status: string;
  upstreamAheadBy: number;
  forkAheadBy: number;
  upstreamCommits: {
    sha: string;
    message: string;
    authorName?: string;
    committedAt?: number;
  }[];
  forkCommits: {
    sha: string;
    message: string;
    authorName?: string;
    committedAt?: number;
  }[];
  importantFilePatterns?: string[];
  ignoredFilePatterns?: string[];
};

export type ReasoningEffort =
  | 'none'
  | 'minimal'
  | 'low'
  | 'medium'
  | 'high'
  | 'xhigh';

export type OpenAiReviewSettings = {
  apiKey: string;
  model: string;
  reasoningEffort: ReasoningEffort;
};

const reviewSchema = {
  type: 'object',
  additionalProperties: false,
  properties: {
    summary: { type: 'string' },
    risk: { type: 'string', enum: ['low', 'medium', 'high'] },
    compatible: { type: 'boolean' },
    requiresHumanReview: { type: 'boolean' },
    recommendedAction: {
      type: 'string',
      enum: ['sync', 'open_review_pr', 'manual_review', 'do_not_sync'],
    },
    potentialConflicts: { type: 'array', items: { type: 'string' } },
    importantFiles: { type: 'array', items: { type: 'string' } },
    reasoningSummary: { type: 'string' },
  },
  required: [
    'summary',
    'risk',
    'compatible',
    'requiresHumanReview',
    'recommendedAction',
    'potentialConflicts',
    'importantFiles',
    'reasoningSummary',
  ],
} as const;

const isReviewRisk = (value: unknown): value is ReviewRisk =>
  value === 'low' || value === 'medium' || value === 'high';

const isReviewAction = (value: unknown): value is ReviewAction =>
  value === 'sync' ||
  value === 'open_review_pr' ||
  value === 'manual_review' ||
  value === 'do_not_sync';

const validateReview = (value: unknown): AiCompatibilityReview => {
  if (!value || typeof value !== 'object') {
    throw new ConvexError('OpenAI returned an invalid review payload.');
  }
  const record = value as Record<string, unknown>;
  if (
    typeof record.summary !== 'string' ||
    !isReviewRisk(record.risk) ||
    typeof record.compatible !== 'boolean' ||
    typeof record.requiresHumanReview !== 'boolean' ||
    !isReviewAction(record.recommendedAction) ||
    !Array.isArray(record.potentialConflicts) ||
    !Array.isArray(record.importantFiles) ||
    typeof record.reasoningSummary !== 'string'
  ) {
    throw new ConvexError('OpenAI review did not match the expected schema.');
  }
  return {
    summary: record.summary,
    risk: record.risk,
    compatible: record.compatible,
    requiresHumanReview: record.requiresHumanReview,
    recommendedAction: record.recommendedAction,
    potentialConflicts: record.potentialConflicts.filter(
      (item): item is string => typeof item === 'string',
    ),
    importantFiles: record.importantFiles.filter(
      (item): item is string => typeof item === 'string',
    ),
    reasoningSummary: record.reasoningSummary,
  };
};

export const reviewUpstreamCompatibility = async (
  input: ReviewInput,
  settings: OpenAiReviewSettings,
): Promise<AiCompatibilityReview> => {
  const response = await new OpenAI({
    apiKey: settings.apiKey,
  }).responses.create({
    model: settings.model,
    store: false,
    reasoning: {
      effort: settings.reasoningEffort,
    },
    input: [
      {
        role: 'system',
        content:
          'You are reviewing whether upstream changes can be safely brought into a maintained fork. You do not execute code. You do not claim tests passed. Treat fork-only commits as user customizations that must be preserved. If changed files overlap with fork-only changes, increase risk. If patch context is incomplete, say so and require human review. Prefer conservative recommendations. Return only the required structured output.',
      },
      {
        role: 'user',
        content: JSON.stringify(input, null, 2),
      },
    ],
    text: {
      format: {
        type: 'json_schema',
        name: 'spoon_upstream_compatibility_review',
        strict: true,
        schema: reviewSchema,
      },
    },
  });
  const raw = response.output_text;
  if (!raw) throw new ConvexError('OpenAI returned an empty review.');
  return validateReview(JSON.parse(raw));
};
