import { generateText, Output, stepCountIs } from 'ai'
import { IS_PLATFORM } from 'common'
import { source } from 'common-tags'
import { NextApiRequest, NextApiResponse } from 'next'
import { z } from 'zod'

import type { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
import { getOrgAIDetails } from '@/lib/ai/ai-details'
import { getModel } from '@/lib/ai/model'
import { DEFAULT_COMPLETION_MODEL } from '@/lib/ai/model.utils'
import { RLS_PROMPT } from '@/lib/ai/prompts'
import { getTools } from '@/lib/ai/tools'
import apiWrapper from '@/lib/api/apiWrapper'

const policySchema = z.object({
  sql: z.string().describe('The generated Postgres CREATE POLICY statement.'),
  name: z.string().describe('The name of the policy.'),
  command: z
    .enum(['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'ALL'])
    .describe('The SQL command this policy applies to.'),
  definition: z
    .string()
    .optional()
    .describe('The USING clause expression (for SELECT, UPDATE, DELETE).'),
  check: z.string().optional().describe('The WITH CHECK clause expression (for INSERT, UPDATE).'),
  action: z
    .enum(['PERMISSIVE', 'RESTRICTIVE'])
    .default('PERMISSIVE')
    .describe('Whether the policy is PERMISSIVE or RESTRICTIVE.'),
  roles: z.array(z.string()).default(['public']).describe('The roles this policy applies to.'),
})

const requestBodySchema = z.object({
  tableName: z.string().min(1),
  schema: z.string().default('public'),
  columns: z.array(z.string()).optional(),
  projectRef: z.string().min(1),
  connectionString: z.string().min(1),
  orgSlug: z.string().optional(),
  message: z.string().optional(),
})

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { method } = req

  switch (method) {
    case 'POST':
      return handlePost(req, res)
    default:
      res.setHeader('Allow', ['POST'])
      res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
  }
}

export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
  const authorization = req.headers.authorization
  const accessToken = authorization?.replace('Bearer ', '')

  if (IS_PLATFORM && !accessToken) {
    return res.status(401).json({ error: 'Authorization token is required' })
  }

  const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
  const { data, error: parseError } = requestBodySchema.safeParse(body)

  if (parseError) {
    return res.status(400).json({ error: 'Invalid request body', issues: parseError.issues })
  }

  const { tableName, schema, columns = [], projectRef, connectionString, orgSlug, message } = data

  let aiOptInLevel: AiOptInLevel = 'disabled'

  if (!IS_PLATFORM) {
    aiOptInLevel = 'schema'
  }

  if (IS_PLATFORM && orgSlug && authorization) {
    try {
      const { aiOptInLevel: orgAIOptInLevel } = await getOrgAIDetails({
        orgSlug,
        authorization,
      })

      aiOptInLevel = orgAIOptInLevel
    } catch (error) {
      return res.status(400).json({
        error: 'There was an error fetching your organization details',
      })
    }
  }

  try {
    const { modelParams, error: modelError } = await getModel({
      provider: 'openai',
      modelEntry: DEFAULT_COMPLETION_MODEL,
    })

    if (modelError) {
      return res.status(500).json({ error: modelError.message })
    }

    const tools = await getTools({
      projectRef,
      connectionString,
      authorization,
      aiOptInLevel,
      accessToken,
    })

    const { experimental_output } = await generateText({
      ...modelParams,
      stopWhen: stepCountIs(5),
      prompt: source`
        You are a Postgres RLS (Row Level Security) expert.
        Determine the most appropriate policies for the "${schema}"."${tableName}" table within a Briven project.

        ${columns.length > 0 ? `Table columns: ${columns.join(', ')}` : 'No column metadata provided.'}

        ${message ? `User request: ${message}` : ''}

        RLS Guide: ${RLS_PROMPT}

        Requirements:
        - Use the available planning and schema tools (like "list_policies" or "list_tables") to inspect the "${schema}" schema and existing policies before generating new ones.
        - Ensure policies strictly adhere to the existing schema
        - Return a curated list of recommended CREATE POLICY statements as JSON.
        - Each policy must include: name, sql, command (SELECT/INSERT/UPDATE/DELETE/ALL), action (PERMISSIVE/RESTRICTIVE), roles (array of role names).
        - Include "definition" (USING clause expression without the USING keyword) for SELECT, UPDATE, DELETE policies.
        - Include "check" (WITH CHECK clause expression without the WITH CHECK keywords) for INSERT, UPDATE policies.
        - Avoid duplicating existing policies and reference the public schema and typical Briven best practices when deciding the coverage.
        - Prefer PERMISSIVE policies unless a RESTRICTIVE policy is explicitly required
      `,
      tools,
      experimental_output: Output.object({
        schema: z.object({
          policies: z.array(policySchema),
        }),
      }),
    })

    // Add table and schema to each policy from the request
    const policies = (experimental_output?.policies ?? []).map((policy) => ({
      ...policy,
      table: tableName,
      schema,
    }))

    return res.json(policies)
  } catch (error) {
    if (error instanceof Error) {
      console.error(`AI policy generation failed: ${error.message}`)
      return res.status(500).json({
        error: 'Failed to generate policy. Please try again.',
      })
    }
    return res.status(500).json({
      error: 'An unknown error occurred.',
    })
  }
}

const wrapper = (req: NextApiRequest, res: NextApiResponse) =>
  apiWrapper(req, res, handler, { withAuth: true })

export default wrapper
