import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
import { GoogleSheetsAdapter } from '../google-sheets-adapter'
import type { ExecutionContext } from '../../../../../../base/types'
import { createClient } from '@supabase/supabase-js'
import type { Database } from '../../../../../../../../supabase/database.types'
import { decryptCredentialValue } from '../../../../../../../../auth/multi-tenancy/credentials/server/service'

/**
 * Google Sheets Integration Tests
 *
 * Consolidated test suite covering:
 * - Basic CRUD operations (read, write, append, batch update, clear)
 * - Workflow-friendly methods (getHeaders, getRowByValue, upsertRow, etc.)
 * - Error handling
 *
 * Rate limit: Google Sheets API allows 60 requests/minute/user
 * Tests are grouped to minimize API calls.
 */

const SKIP_TESTS = true

describe.skipIf(SKIP_TESTS)('Google Sheets Integration Tests', () => {
  const adapter = new GoogleSheetsAdapter()
  const organizationId = 'f9aa5a56-8c13-4cd1-9161-8827ae7b452b'
  const credentialName = 'elevasis-google-sheets'
  const spreadsheetId = '1cgrLVPE0bpq4GAqo8RvRh3VkP49Ut-osuNKL-hNx44Y'

  const context: ExecutionContext = {
    organizationId,
    executionId: 'sheets-integration-test',
    resourceId: 'sheets-test-agent',
    resourceType: 'agent',
    logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), child: vi.fn().mockReturnThis() }
  }

  let credentials: Record<string, unknown>
  let testSheetName: string

  beforeAll(async () => {
    console.log('\n=== Google Sheets Integration Tests ===')
    const supabase = createClient<Database>(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!)

    const { data: credRow, error } = await supabase
      .from('credentials')
      .select('encrypted_value')
      .eq('organization_id', organizationId)
      .eq('name', credentialName)
      .single()

    if (error || !credRow) {
      throw new Error(`Credential not found: ${error?.message}`)
    }

    credentials = decryptCredentialValue(credRow.encrypted_value)
    console.log('✓ Credentials loaded\n')
  }, 15000)

  afterAll(async () => {
    // Clean up test data
    const rangesToClean = ['Test!A100:C105', 'Test!A200:C210', 'Test!A300:C302']
    for (const range of rangesToClean) {
      try {
        await adapter.call('clearRange', { spreadsheetId, range }, credentials, context)
      } catch {
        // Ignore cleanup errors
      }
    }
    console.log('\n=== Tests Complete ===\n')
  })

  // ============================================
  // 1. Basic CRUD Operations
  // ============================================

  describe('Basic CRUD Operations', () => {
    it('should get spreadsheet metadata', async () => {
      const result = (await adapter.call('getSpreadsheetMetadata', { spreadsheetId }, credentials, context)) as {
        title: string
        sheets: Array<{ sheetId: number; title: string; index: number }>
      }

      expect(result.title).toBeTruthy()
      expect(result.sheets.length).toBeGreaterThan(0)
      testSheetName = result.sheets[0].title
      console.log(`Using sheet: ${testSheetName}`)
    }, 15000)

    it('should read, write, and clear data', async () => {
      const range = 'Test!A100:C101'
      const testData = [
        ['TestName', '101', 'Test description'],
        ['TestName2', '102', 'Another description']
      ]

      // Write
      const writeResult = (await adapter.call(
        'writeSheet',
        { spreadsheetId, range, values: testData },
        credentials,
        context
      )) as { updatedCells: number }
      expect(writeResult.updatedCells).toBe(6)

      // Read back
      const readResult = (await adapter.call('readSheet', { spreadsheetId, range }, credentials, context)) as {
        values: string[][]
      }
      expect(readResult.values[0][0]).toBe('TestName')
      expect(readResult.values[1][0]).toBe('TestName2')

      // Clear
      const clearResult = (await adapter.call('clearRange', { spreadsheetId, range }, credentials, context)) as {
        clearedRange: string
      }
      expect(clearResult.clearedRange).toContain('A100')

      // Verify cleared
      const verifyResult = (await adapter.call('readSheet', { spreadsheetId, range }, credentials, context)) as {
        values: string[][]
      }
      expect(verifyResult.values.length).toBe(0)
    }, 15000)

    it('should append rows and batch update', async () => {
      // Append
      const appendResult = (await adapter.call(
        'appendRows',
        {
          spreadsheetId,
          range: 'Test!A200:C200',
          values: [['Appended1', '201', 'Appended row']]
        },
        credentials,
        context
      )) as { updatedRows: number }
      expect(appendResult.updatedRows).toBe(1)

      // Batch update
      const batchResult = (await adapter.call(
        'batchUpdate',
        {
          spreadsheetId,
          data: [
            { range: 'Test!A202:C202', values: [['Batch1', '202', 'Batch row 1']] },
            { range: 'Test!A203:C203', values: [['Batch2', '203', 'Batch row 2']] }
          ]
        },
        credentials,
        context
      )) as { totalUpdatedCells: number; totalUpdatedRows: number }
      expect(batchResult.totalUpdatedRows).toBe(2)
    }, 20000)
  })

  // ============================================
  // 2. Workflow-Friendly Methods
  // ============================================

  describe('Workflow-Friendly Methods', () => {
    it('should get headers and find row by value', async () => {
      // Setup: Write test data with headers
      await adapter.call(
        'writeSheet',
        {
          spreadsheetId,
          range: 'Test!A1:C2',
          values: [
            ['Name', 'Id', 'Description'],
            ['FindMe', '999', 'Test row for lookup']
          ]
        },
        credentials,
        context
      )

      // Get headers
      const headersResult = (await adapter.call(
        'getHeaders',
        { spreadsheetId, sheetName: 'Test' },
        credentials,
        context
      )) as { headers: string[]; columnMap: Record<string, string> }
      expect(headersResult.headers).toContain('Name')
      expect(headersResult.columnMap.Name).toBe('A')

      // Find row by value
      const findResult = (await adapter.call(
        'getRowByValue',
        { spreadsheetId, sheetName: 'Test', searchColumn: 'A', searchValue: 'FindMe' },
        credentials,
        context
      )) as { found: boolean; rowNumber: number | null; rowData: string[] | null }
      expect(findResult.found).toBe(true)
      expect(findResult.rowData?.[0]).toBe('FindMe')
    }, 20000)

    it('should upsert and update rows by value', async () => {
      const uniqueKey = `UpsertTest_${Date.now()}`

      // Upsert (insert)
      const insertResult = (await adapter.call(
        'upsertRow',
        {
          spreadsheetId,
          sheetName: 'Test',
          keyColumn: 'A',
          keyValue: uniqueKey,
          rowData: { Name: uniqueKey, Id: '888', Description: 'Inserted via upsert' }
        },
        credentials,
        context
      )) as { action: 'inserted' | 'updated' }
      expect(insertResult.action).toBe('inserted')

      // Upsert (update)
      const updateResult = (await adapter.call(
        'upsertRow',
        {
          spreadsheetId,
          sheetName: 'Test',
          keyColumn: 'A',
          keyValue: uniqueKey,
          rowData: { Name: uniqueKey, Id: '888', Description: 'Updated via upsert' }
        },
        credentials,
        context
      )) as { action: 'inserted' | 'updated' }
      expect(updateResult.action).toBe('updated')

      // Clean up
      await adapter.call(
        'deleteRowByValue',
        { spreadsheetId, sheetName: 'Test', searchColumn: 'A', searchValue: uniqueKey },
        credentials,
        context
      )
    }, 25000)
  })

  // ============================================
  // 3. Error Handling
  // ============================================

  describe('Error Handling', () => {
    it('should handle invalid spreadsheet ID', async () => {
      await expect(
        adapter.call('readSheet', { spreadsheetId: 'invalid-id', range: 'Sheet1!A1' }, credentials, context)
      ).rejects.toThrow()
    }, 15000)

    it('should validate required fields', async () => {
      await expect(adapter.call('readSheet', { spreadsheetId: '', range: 'A1' }, credentials, context)).rejects.toThrow(
        'Missing required field: spreadsheetId'
      )

      await expect(
        adapter.call('writeSheet', { spreadsheetId, range: 'A1', values: null }, credentials, context)
      ).rejects.toThrow('Missing required field: values')
    }, 15000)
  })
})
