import { assertEquals, assertExists } from 'https://deno.land/std@0.220.1/assert/mod.ts'
import { createClient, SupabaseClient } from '../../dist/module/index.js'
import type { RealtimeChannel } from '../../dist/module/index.d.ts'

// These tests assume that a local Supabase server is already running
// Start a local Supabase instance with 'supabase start' before running these tests

// TODO: Remove sanitizeOps and sanitizeResources once the issue is fixed
Deno.test(
  'Supabase Integration Tests',
  { sanitizeOps: false, sanitizeResources: false },
  async (t) => {
    // Default local dev credentials from Supabase CLI
    const SUPABASE_URL = 'http://127.0.0.1:54321'
    const ANON_KEY =
      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'

    const supabase = createClient(SUPABASE_URL, ANON_KEY, {
      realtime: { heartbeatIntervalMs: 500 },
    })

    // Cleanup function to be called after all tests
    const cleanup = async () => {
      await supabase.auth.signOut()
      await supabase.auth.stopAutoRefresh()
      await supabase.removeAllChannels()
      // Give some time for cleanup to complete
      await new Promise((resolve) => setTimeout(resolve, 1000))
    }

    try {
      await t.step('should connect to Supabase instance', () => {
        assertExists(supabase)
        assertEquals(supabase instanceof SupabaseClient, true)
      })

      await t.step('PostgREST - should query data from public schema', async () => {
        const { data, error } = await supabase.from('todos').select('*').limit(5)

        // The default schema includes a 'todos' table, but it might be empty
        assertEquals(error, null)
        assertEquals(Array.isArray(data), true)
      })

      await t.step('PostgREST - should create and delete a todo', async () => {
        // Create a new todo
        const { data: createdTodo, error: createError } = await supabase
          .from('todos')
          .insert({ task: 'Integration Test Todo', is_complete: false })
          .select()
          .single()

        assertEquals(createError, null)
        assertExists(createdTodo)
        assertEquals(createdTodo!.task, 'Integration Test Todo')
        assertEquals(createdTodo!.is_complete, false)

        // Delete the created todo
        const { error: deleteError } = await supabase
          .from('todos')
          .delete()
          .eq('id', createdTodo!.id)

        assertEquals(deleteError, null)

        // Verify the todo was deleted
        const { data: fetchedTodo, error: fetchError } = await supabase
          .from('todos')
          .select('*')
          .eq('id', createdTodo!.id)
          .single()

        assertExists(fetchError)
        assertEquals(fetchedTodo, null)
      })

      await t.step('Authentication - should sign up a user', async () => {
        const email = `test-${Date.now()}@example.com`
        const password = 'password123'

        const { data, error } = await supabase.auth.signUp({
          email,
          password,
        })

        assertEquals(error, null)
        assertExists(data.user)
        assertEquals(data.user!.email, email)
      })

      await t.step('Authentication - should sign in and out successfully', async () => {
        const email = `deno-signout-${Date.now()}@example.com`
        const password = 'password123'

        await supabase.auth.signUp({ email, password })
        const { data, error } = await supabase.auth.signInWithPassword({ email, password })

        assertEquals(error, null)
        assertExists(data.user)
        assertEquals(data.user!.email, email)

        const { error: signOutError } = await supabase.auth.signOut()

        assertEquals(signOutError, null)
      })

      await t.step('Authentication - should get current user', async () => {
        const email = `deno-getuser-${Date.now()}@example.com`
        const password = 'password123'

        await supabase.auth.signUp({ email, password })
        await supabase.auth.signInWithPassword({ email, password })

        const { data, error } = await supabase.auth.getUser()

        assertEquals(error, null)
        assertExists(data.user)
        assertEquals(data.user!.email, email)
      })

      await t.step('Authentication - should handle invalid credentials', async () => {
        const email = `deno-invalid-${Date.now()}@example.com`
        const password = 'password123'

        await supabase.auth.signUp({ email, password })

        const { data, error } = await supabase.auth.signInWithPassword({
          email,
          password: 'wrongpassword',
        })

        assertExists(error)
        assertEquals(data.user, null)
      })

      await t.step('Authentication - should handle non-existent user', async () => {
        const email = `deno-nonexistent-${Date.now()}@example.com`
        const password = 'password123'

        const { data, error } = await supabase.auth.signInWithPassword({
          email,
          password,
        })

        assertExists(error)
        assertEquals(data.user, null)
      })

      await t.step('Realtime - is able to connect and broadcast', async () => {
        const channelName = `channel-${crypto.randomUUID()}`
        let channel: RealtimeChannel
        const email = `test-${Date.now()}@example.com`
        const password = 'password123'

        // Sign up and create channel
        await supabase.auth.signUp({ email, password })
        const config = { broadcast: { self: true }, private: true }
        channel = supabase.channel(channelName, { config })

        const testMessage = { message: 'test' }
        let receivedMessage: any
        let subscribed = false
        let attempts = 0

        channel
          .on('broadcast', { event: '*' }, (payload: unknown) => (receivedMessage = payload))
          .subscribe((status: string) => {
            if (status == 'SUBSCRIBED') subscribed = true
          })

        // Wait for subscription
        while (!subscribed) {
          if (attempts > 50) throw new Error('Timeout waiting for subscription')
          await new Promise((resolve) => setTimeout(resolve, 100))
          attempts++
        }

        attempts = 0

        channel.send({
          type: 'broadcast',
          event: 'test-event',
          payload: testMessage,
        })

        // Wait on message
        while (!receivedMessage) {
          if (attempts > 50) throw new Error('Timeout waiting for message')
          await new Promise((resolve) => setTimeout(resolve, 100))
          attempts++
        }

        assertExists(receivedMessage)
        assertEquals(supabase.realtime.getChannels().length, 1)

        // Cleanup channel
        await channel.unsubscribe()
      })

      await t.step('Storage - should upload and list file in bucket', async () => {
        const bucket = 'test-bucket'
        const filePath = 'test-file.txt'
        const fileContent = new Blob(['Hello, Supabase Storage!'], { type: 'text/plain' })

        // use service_role key for bypass RLS
        const SERVICE_ROLE_KEY =
          Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ||
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'
        const supabaseWithServiceRole = createClient(SUPABASE_URL, SERVICE_ROLE_KEY, {
          realtime: { heartbeatIntervalMs: 500 },
        })

        // upload
        const { data: uploadData, error: uploadError } = await supabaseWithServiceRole.storage
          .from(bucket)
          .upload(filePath, fileContent, { upsert: true })
        assertEquals(uploadError, null)
        assertExists(uploadData)

        // list
        const { data: listData, error: listError } = await supabaseWithServiceRole.storage
          .from(bucket)
          .list()
        assertEquals(listError, null)
        assertEquals(Array.isArray(listData), true)
        if (!listData) throw new Error('listData is null')
        const fileNames = listData.map((f: any) => f.name)
        assertEquals(fileNames.includes('test-file.txt'), true)

        // delete file
        const { error: deleteError } = await supabaseWithServiceRole.storage
          .from(bucket)
          .remove([filePath])
        assertEquals(deleteError, null)
      })
    } finally {
      // Ensure cleanup runs even if tests fail
      await cleanup()
    }
  }
)
