/**
 * Dropbox Create Folder Unit Tests
 *
 * Tests the createFolder fetch implementation:
 * - Request formatting (headers, body)
 * - Response parsing
 * - 409 conflict handling (folder exists)
 * - Error handling and mapping
 *
 * Mock Boundaries:
 * - global.fetch -> mock HTTP responses
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { createFolder } from '../index'
import type { ExecutionContext } from '../../../../../../../../base/types'

// ============================================================================
// Mock Setup
// ============================================================================

const mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)

// ============================================================================
// Test Data
// ============================================================================

const credentials = { accessToken: 'sl.test_access_token' }

const mockContext: ExecutionContext = {
  executionId: 'test-exec-123',
  organizationId: 'org-test-456',
  resourceId: 'test-resource',
  resourceType: 'workflow',
  logger: {
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    debug: vi.fn()
  }
}

const successResponse = {
  metadata: {
    id: 'id:folder123',
    name: 'Contracts',
    path_lower: '/clients/acme/contracts',
    path_display: '/Clients/Acme/Contracts'
  }
}

// ============================================================================
// Tests
// ============================================================================

describe('createFolder', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  afterEach(() => {
    vi.resetAllMocks()
  })

  // ==========================================================================
  // Request Formatting
  // ==========================================================================

  describe('request formatting', () => {
    it('sends POST to correct Dropbox RPC endpoint', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/TestFolder' }, mockContext)

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.dropboxapi.com/2/files/create_folder_v2',
        expect.any(Object)
      )
    })

    it('uses application/json Content-Type for RPC endpoint', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/TestFolder' }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      expect(options.headers['Content-Type']).toBe('application/json')
    })

    it('includes Bearer token in Authorization header', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/TestFolder' }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      expect(options.headers['Authorization']).toBe('Bearer sl.test_access_token')
    })

    it('sends path in JSON body', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/Clients/Acme/Contracts' }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      const body = JSON.parse(options.body)
      expect(body.path).toBe('/Clients/Acme/Contracts')
    })

    it('sets autorename to false by default', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/TestFolder' }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      const body = JSON.parse(options.body)
      expect(body.autorename).toBe(false)
    })

    it('respects autorename parameter when true', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/TestFolder', autorename: true }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      const body = JSON.parse(options.body)
      expect(body.autorename).toBe(true)
    })
  })

  // ==========================================================================
  // Response Handling
  // ==========================================================================

  describe('response handling', () => {
    it('returns folder metadata on success', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      const result = await createFolder(credentials, { path: '/Clients/Acme/Contracts' }, mockContext)

      expect(result).toEqual(successResponse)
      expect(result.metadata).toBeDefined()
      expect(result.metadata?.id).toBe('id:folder123')
      expect(result.metadata?.path_display).toBe('/Clients/Acme/Contracts')
    })

    it('preserves all metadata fields from response', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      const result = await createFolder(credentials, { path: '/TestFolder' }, mockContext)

      expect(result.metadata).toHaveProperty('id')
      expect(result.metadata).toHaveProperty('name')
      expect(result.metadata).toHaveProperty('path_lower')
      expect(result.metadata).toHaveProperty('path_display')
    })
  })

  // ==========================================================================
  // 409 Conflict Handling (Critical Business Logic)
  // ==========================================================================

  describe('409 conflict handling', () => {
    it('returns null metadata for path/conflict (folder exists)', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/conflict/folder/...' })
      })

      const result = await createFolder(credentials, { path: '/ExistingFolder' }, mockContext)

      // This is critical business logic - folder already exists is NOT an error
      expect(result).toEqual({ metadata: null })
    })

    it('returns null for path/conflict/file (file exists at path)', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/conflict/file/...' })
      })

      const result = await createFolder(credentials, { path: '/ExistingFile' }, mockContext)

      expect(result).toEqual({ metadata: null })
    })

    it('returns null for generic path/conflict', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/conflict' })
      })

      const result = await createFolder(credentials, { path: '/Existing' }, mockContext)

      expect(result).toEqual({ metadata: null })
    })

    it('throws api_error for path/not_found (parent does not exist)', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/not_found/...' })
      })

      await expect(createFolder(credentials, { path: '/Invalid/Path/Folder' }, mockContext)).rejects.toThrow(
        'path/not_found'
      )
    })

    it('throws api_error for path/restricted_content', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/restricted_content/...' })
      })

      await expect(createFolder(credentials, { path: '/Restricted' }, mockContext)).rejects.toThrow('path/restricted_content')
    })

    it('throws api_error for other 409 errors', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/disallowed_name/...' })
      })

      await expect(createFolder(credentials, { path: '/.invalid' }, mockContext)).rejects.toThrow('path/disallowed_name')
    })
  })

  // ==========================================================================
  // Error Handling
  // ==========================================================================

  describe('error handling', () => {
    it('throws credentials_invalid for 401 Unauthorized', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: () => Promise.resolve({ error_summary: 'expired_access_token/...' })
      })

      await expect(createFolder(credentials, { path: '/TestFolder' }, mockContext)).rejects.toThrow(
        'Dropbox access token expired or invalid'
      )
    })

    it('throws rate_limited for 429 Too Many Requests', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 429,
        json: () => Promise.resolve({ error_summary: 'too_many_requests/...' })
      })

      await expect(createFolder(credentials, { path: '/TestFolder' }, mockContext)).rejects.toThrow('too_many_requests')
    })

    it('throws api_error for 400 Bad Request', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: () => Promise.resolve({ error_summary: 'path/malformed_path/...' })
      })

      await expect(createFolder(credentials, { path: 'invalid' }, mockContext)).rejects.toThrow('path/malformed_path')
    })

    it('throws api_error for 500 Server Error', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
        json: () => Promise.resolve({ error_summary: 'internal_error' })
      })

      await expect(createFolder(credentials, { path: '/TestFolder' }, mockContext)).rejects.toThrow('internal_error')
    })

    it('includes error_summary in error message', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: () => Promise.resolve({ error_summary: 'path/malformed_path/...' })
      })

      await expect(createFolder(credentials, { path: 'bad' }, mockContext)).rejects.toThrow('path/malformed_path')
    })

    it('handles non-JSON error responses gracefully', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 503,
        statusText: 'Service Unavailable',
        json: () => Promise.reject(new Error('Not JSON'))
      })

      await expect(createFolder(credentials, { path: '/TestFolder' }, mockContext)).rejects.toThrow()
    })
  })

  // ==========================================================================
  // Edge Cases
  // ==========================================================================

  describe('edge cases', () => {
    it('handles root-level folders', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            metadata: {
              id: 'id:root123',
              name: 'Clients',
              path_lower: '/clients',
              path_display: '/Clients'
            }
          })
      })

      const result = await createFolder(credentials, { path: '/Clients' }, mockContext)

      expect(result.metadata?.path_display).toBe('/Clients')
    })

    it('handles deeply nested paths', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            metadata: {
              id: 'id:deep123',
              name: 'Deep',
              path_lower: '/level1/level2/level3/level4/deep',
              path_display: '/Level1/Level2/Level3/Level4/Deep'
            }
          })
      })

      const result = await createFolder(
        credentials,
        { path: '/Level1/Level2/Level3/Level4/Deep' },
        mockContext
      )

      expect(result.metadata?.path_display).toBe('/Level1/Level2/Level3/Level4/Deep')
    })

    it('handles paths with spaces', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            metadata: {
              id: 'id:spaces123',
              name: 'My Folder',
              path_lower: '/my folder',
              path_display: '/My Folder'
            }
          })
      })

      await createFolder(credentials, { path: '/My Folder' }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      const body = JSON.parse(options.body)
      expect(body.path).toBe('/My Folder')
    })

    it('handles paths with special characters', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            metadata: {
              id: 'id:special123',
              name: "Client's Files (2024)",
              path_lower: "/client's files (2024)",
              path_display: "/Client's Files (2024)"
            }
          })
      })

      await createFolder(credentials, { path: "/Client's Files (2024)" }, mockContext)

      const options = mockFetch.mock.calls[0][1]
      const body = JSON.parse(options.body)
      expect(body.path).toBe("/Client's Files (2024)")
    })

    it('works without context parameter', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      const result = await createFolder(credentials, { path: '/TestFolder' })

      expect(result).toEqual(successResponse)
    })

    it('logs debug message when folder created', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(successResponse)
      })

      await createFolder(credentials, { path: '/TestFolder' }, mockContext)

      expect(mockContext.logger?.debug).toHaveBeenCalledWith('Creating folder in Dropbox: /TestFolder')
    })

    it('logs debug message when folder already exists', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: () => Promise.resolve({ error_summary: 'path/conflict/folder' })
      })

      await createFolder(credentials, { path: '/ExistingFolder' }, mockContext)

      expect(mockContext.logger?.debug).toHaveBeenCalledWith('Folder already exists: /ExistingFolder')
    })
  })
})
