import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import WorkoutForm from './workout-form'

// Mock Next.js router
const mockPush = vi.fn()
vi.mock('next/navigation', () => ({
  useRouter: () => ({
    push: mockPush,
    back: vi.fn(),
    forward: vi.fn(),
    refresh: vi.fn(),
    replace: vi.fn(),
    prefetch: vi.fn(),
  }),
}))

// Create mock functions that can be manipulated per test
const mockSelect = vi.fn()
const mockOrder = vi.fn()
const mockInsert = vi.fn()
const mockInsertSelect = vi.fn()
const mockInsertSelectSingle = vi.fn()
const mockUpdate = vi.fn()
const mockUpdateEq = vi.fn()
const mockUpdateEqSelect = vi.fn()
const mockUpdateEqSelectSingle = vi.fn()
const mockFrom = vi.fn()

// Mock Supabase with dynamic mock functions
vi.mock('@/lib/supabase', () => ({
  supabase: {
    from: (...args: any[]) => mockFrom(...args),
    storage: {
      from: vi.fn(() => ({
        move: vi.fn(() => Promise.resolve({ error: null })),
        getPublicUrl: vi.fn(() => ({ data: { publicUrl: 'https://example.com/file.jpg' } })),
      })),
    },
  },
}))

// Mock storage functions
const mockMoveFiles = vi.fn()
vi.mock('@/lib/storage', () => ({
  moveFilesFromTempToWorkout: (...args: any[]) => mockMoveFiles(...args),
  UPLOAD_CONFIGS: {
    video: {
      bucket: 'workout-videos',
      maxSize: 500 * 1024 * 1024,
      allowedTypes: ['video/mp4', 'video/webm', 'video/quicktime'],
    },
    thumbnail: {
      bucket: 'workout-thumbnails',
      maxSize: 10 * 1024 * 1024,
      allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
    },
    avatar: {
      bucket: 'trainer-avatars',
      maxSize: 5 * 1024 * 1024,
      allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
    },
  },
}))

// Helper to setup default successful mocks
const setupDefaultMocks = () => {
  // Reset all mocks
  mockSelect.mockReset()
  mockOrder.mockReset()
  mockInsert.mockReset()
  mockInsertSelect.mockReset()
  mockInsertSelectSingle.mockReset()
  mockUpdate.mockReset()
  mockUpdateEq.mockReset()
  mockUpdateEqSelect.mockReset()
  mockUpdateEqSelectSingle.mockReset()
  mockFrom.mockReset()
  mockMoveFiles.mockReset()

  // Setup trainers query chain
  mockOrder.mockResolvedValue({
    data: [{ id: 'trainer-1', name: 'Test Trainer' }],
    error: null,
  })
  mockSelect.mockReturnValue({ order: mockOrder })

  // Setup insert chain for create mode
  mockInsertSelectSingle.mockResolvedValue({
    data: { id: 'new-workout-id' },
    error: null,
  })
  mockInsertSelect.mockReturnValue({ single: mockInsertSelectSingle })
  mockInsert.mockReturnValue({ select: mockInsertSelect })

  // Setup update chain for edit mode
  mockUpdateEqSelectSingle.mockResolvedValue({
    data: { id: 'test-id' },
    error: null,
  })
  mockUpdateEqSelect.mockReturnValue({ single: mockUpdateEqSelectSingle })
  mockUpdateEq.mockReturnValue({ select: mockUpdateEqSelect })
  mockUpdate.mockReturnValue({ eq: mockUpdateEq })

  // Setup main from mock
  mockFrom.mockImplementation((table: string) => {
    if (table === 'trainers') {
      return { select: mockSelect }
    }
    if (table === 'workouts') {
      return {
        select: mockSelect,
        insert: mockInsert,
        update: mockUpdate,
      }
    }
    return {
      select: mockSelect,
      insert: mockInsert,
      update: mockUpdate,
    }
  })

  // Setup move files mock
  mockMoveFiles.mockResolvedValue({
    thumbnail: null,
    video: null,
    errors: [],
  })
}

describe('WorkoutForm', () => {
  beforeEach(() => {
    setupDefaultMocks()
    mockPush.mockClear()
  })

  describe('rendering', () => {
    it('should render form in create mode', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })
      
      expect(screen.getByRole('tab', { name: /basics/i })).toBeInTheDocument()
      expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      expect(screen.getByRole('tab', { name: /content/i })).toBeInTheDocument()
      expect(screen.getByRole('tab', { name: /media/i })).toBeInTheDocument()
    })

    it('should render form in edit mode', async () => {
      const initialData = {
        id: 'test-id',
        title: 'Test Workout',
        trainer_id: 'trainer-1',
        category: 'full-body' as const,
        level: 'Beginner' as const,
        duration: 4,
        rounds: 8,
        prep_time: 10,
        work_time: 20,
        rest_time: 10,
        calories: 45,
        equipment: [],
        music_vibe: 'electronic' as const,
        exercises: [{ name: 'Push-ups', duration: 20 }],
        thumbnail_url: null,
        video_url: null,
        is_featured: false,
        created_at: '2024-01-01',
        updated_at: '2024-01-01',
      }

      render(<WorkoutForm mode="edit" initialData={initialData} />)

      await waitFor(() => {
        expect(screen.getByDisplayValue('Test Workout')).toBeInTheDocument()
      })
    })

    it('should show loading state for trainers initially', async () => {
      render(<WorkoutForm mode="create" />)

      expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
    })
  })

  describe('form fields', () => {
    it('should update title field', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })

      const titleInput = screen.getByLabelText(/workout title/i)
      await userEvent.type(titleInput, 'My Workout')

      expect(titleInput).toHaveValue('My Workout')
    })

    it('should toggle featured workout', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('switch')).toBeInTheDocument()
      })

      const toggle = screen.getByRole('switch')
      expect(toggle).toHaveAttribute('aria-checked', 'false')

      await userEvent.click(toggle)
      expect(toggle).toHaveAttribute('aria-checked', 'true')
    })
  })

  describe('tab navigation', () => {
    it('should navigate to Timing tab', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      expect(screen.getByRole('tab', { name: /timing/i })).toHaveAttribute('data-state', 'active')
    })

    it('should navigate to Content tab', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /content/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /content/i }))

      expect(screen.getByRole('tab', { name: /content/i })).toHaveAttribute('data-state', 'active')
      expect(screen.getByText(/add exercise/i)).toBeInTheDocument()
    })

    it('should navigate to Media tab', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /media/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /media/i }))

      expect(screen.getByRole('tab', { name: /media/i })).toHaveAttribute('data-state', 'active')
    })

    it('should show music vibe in Media tab', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /media/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /media/i }))

      expect(screen.getByLabelText(/music vibe/i)).toBeInTheDocument()
    })

    it('should persist data when switching tabs', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })

      await userEvent.type(screen.getByLabelText(/workout title/i), 'Test Workout')
      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))
      await userEvent.click(screen.getByRole('tab', { name: /basics/i }))

      expect(screen.getByDisplayValue('Test Workout')).toBeInTheDocument()
    })
  })

  describe('validation', () => {
    it('should show title validation error', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /create workout/i })).toBeInTheDocument()
      })

      const submitButton = screen.getByRole('button', { name: /create workout/i })
      await userEvent.click(submitButton)

      await waitFor(() => {
        expect(screen.getByText(/title is required/i)).toBeInTheDocument()
      })
    })

    it('should clear validation error when field is filled', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /create workout/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('button', { name: /create workout/i }))
      
      await waitFor(() => {
        expect(screen.getByText(/title is required/i)).toBeInTheDocument()
      }, { timeout: 3000 })

      await userEvent.type(screen.getByLabelText(/workout title/i), 'Test Workout')
      expect(screen.getByDisplayValue('Test Workout')).toBeInTheDocument()
    })
  })

  describe('cancel action', () => {
    it('should navigate back on cancel in create mode', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument()
      })

      const cancelButton = screen.getByRole('button', { name: /cancel/i })
      await userEvent.click(cancelButton)

      expect(mockPush).toHaveBeenCalledWith('/workouts')
    })

    it('should navigate to workout detail on cancel in edit mode', async () => {
      const initialData = {
        id: 'test-id',
        title: 'Test Workout',
        trainer_id: 'trainer-1',
        category: 'full-body' as const,
        level: 'Beginner' as const,
        duration: 4,
        rounds: 8,
        prep_time: 10,
        work_time: 20,
        rest_time: 10,
        calories: 45,
        equipment: [],
        music_vibe: 'electronic' as const,
        exercises: [{ name: 'Push-ups', duration: 20 }],
        thumbnail_url: null,
        video_url: null,
        is_featured: false,
        created_at: '2024-01-01',
        updated_at: '2024-01-01',
      }

      render(<WorkoutForm mode="edit" initialData={initialData} />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument()
      })

      const cancelButton = screen.getByRole('button', { name: /cancel/i })
      await userEvent.click(cancelButton)

      expect(mockPush).toHaveBeenCalledWith('/workouts/test-id')
    })
  })

  describe('timing fields', () => {
    it('should update rounds field', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const roundsInput = screen.getByLabelText(/rounds/i)
      await userEvent.clear(roundsInput)
      await userEvent.type(roundsInput, '12')

      expect(roundsInput).toHaveValue(12)
    })

    it('should update calories field', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const caloriesInput = screen.getByLabelText(/calories/i)
      await userEvent.clear(caloriesInput)
      await userEvent.type(caloriesInput, '60')

      expect(caloriesInput).toHaveValue(60)
    })

    it('should update prep time field', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const prepInput = screen.getByLabelText(/prep time/i)
      await userEvent.clear(prepInput)
      await userEvent.type(prepInput, '15')

      expect(prepInput).toHaveValue(15)
    })

    it('should update work time field', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const workInput = screen.getByLabelText(/work time/i)
      await userEvent.clear(workInput)
      await userEvent.type(workInput, '30')

      expect(workInput).toHaveValue(30)
    })

    it('should update rest time field', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const restInput = screen.getByLabelText(/rest time/i)
      await userEvent.clear(restInput)
      await userEvent.type(restInput, '15')

      expect(restInput).toHaveValue(15)
    })
  })

  describe('default values', () => {
    it('should have correct default timing values', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const inputs = screen.getAllByRole('spinbutton')
      const values = inputs.map(input => (input as HTMLInputElement).value)
      
      expect(values).toContain('10')
      expect(values).toContain('20')
    })
  })

  describe('select fields', () => {
    it('should change category', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/category/i)).toBeInTheDocument()
      })

      const categorySelect = screen.getByLabelText(/category/i)
      await userEvent.click(categorySelect)
    })

    it('should change level', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/level/i)).toBeInTheDocument()
      })

      const levelSelect = screen.getByLabelText(/level/i)
      await userEvent.click(levelSelect)
    })

    it('should change duration', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /timing/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /timing/i }))

      const durationSelect = screen.getByLabelText(/duration/i)
      await userEvent.click(durationSelect)
    })

    it('should change music vibe', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('tab', { name: /media/i })).toBeInTheDocument()
      })

      await userEvent.click(screen.getByRole('tab', { name: /media/i }))

      const musicVibeSelect = screen.getByLabelText(/music vibe/i)
      await userEvent.click(musicVibeSelect)
    })
  })

  describe('form submission', () => {
    it('should validate all required fields on submit', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /create workout/i })).toBeInTheDocument()
      })

      // Try to submit without filling any fields
      await userEvent.click(screen.getByRole('button', { name: /create workout/i }))

      // Should show validation errors
      await waitFor(() => {
        expect(screen.getByText(/title is required/i)).toBeInTheDocument()
      })

      // Check for other validation errors that should appear
      const errors = screen.queryAllByText(/is required/i)
      expect(errors.length).toBeGreaterThan(0)
    })

    it('should handle trainer selection error gracefully', async () => {
      const consoleErrorMock = vi.spyOn(console, 'error').mockImplementation(() => {})
      
      // Mock trainers fetch to fail
      mockOrder.mockResolvedValue({
        data: null,
        error: { message: 'Database error' }
      })

      render(<WorkoutForm mode="create" />)

      // Wait a bit for the error to be logged
      await new Promise(resolve => setTimeout(resolve, 100))

      // Should have logged the error
      await waitFor(() => {
        expect(consoleErrorMock).toHaveBeenCalledWith('Failed to load trainers:', expect.any(Object))
      })

      consoleErrorMock.mockRestore()
    })

    it('should successfully submit form in create mode', async () => {
      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })

      // Fill in required fields
      await userEvent.type(screen.getByLabelText(/workout title/i), 'Test Workout')
      
      // Select trainer
      await userEvent.click(screen.getByLabelText(/trainer/i))
      await userEvent.click(screen.getByText('Test Trainer'))

      const submitButton = screen.getByRole('button', { name: /create workout/i })
      await userEvent.click(submitButton)

      // Should show loading state
      await waitFor(() => {
        expect(screen.getByText(/saving/i)).toBeInTheDocument()
      })

      // Verify insert was called
      await waitFor(() => {
        expect(mockInsert).toHaveBeenCalled()
      })

      // Should navigate after success
      await waitFor(() => {
        expect(mockPush).toHaveBeenCalledWith('/workouts/new-workout-id')
      })
    })

    it('should successfully submit form in edit mode', async () => {
      const initialData = {
        id: 'test-id',
        title: 'Test Workout',
        trainer_id: 'trainer-1',
        category: 'full-body' as const,
        level: 'Beginner' as const,
        duration: 4,
        rounds: 8,
        prep_time: 10,
        work_time: 20,
        rest_time: 10,
        calories: 45,
        equipment: [],
        music_vibe: 'electronic' as const,
        exercises: [{ name: 'Push-ups', duration: 20 }],
        thumbnail_url: null,
        video_url: null,
        is_featured: false,
        created_at: '2024-01-01',
        updated_at: '2024-01-01',
      }

      render(<WorkoutForm mode="edit" initialData={initialData} />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /update workout/i })).toBeInTheDocument()
      })

      const submitButton = screen.getByRole('button', { name: /update workout/i })
      await userEvent.click(submitButton)

      // Should show loading state
      await waitFor(() => {
        expect(screen.getByText(/saving/i)).toBeInTheDocument()
      })

      // Verify update was called
      await waitFor(() => {
        expect(mockUpdate).toHaveBeenCalled()
      })

      // Should navigate after success
      await waitFor(() => {
        expect(mockPush).toHaveBeenCalledWith('/workouts/test-id')
      })
    })

    it('should handle file movement during create', async () => {
      // Setup mock to return files that need to be moved
      mockMoveFiles.mockResolvedValue({
        thumbnail: {
          oldPath: 'temp/thumb.jpg',
          newPath: 'new-workout-id/thumb.jpg',
          newUrl: 'https://example.com/new-thumb.jpg'
        },
        video: {
          oldPath: 'temp/video.mp4',
          newPath: 'new-workout-id/video.mp4',
          newUrl: 'https://example.com/new-video.mp4'
        },
        errors: []
      })

      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })

      // Fill in required fields
      await userEvent.type(screen.getByLabelText(/workout title/i), 'Test Workout')
      
      // Select trainer
      await userEvent.click(screen.getByLabelText(/trainer/i))
      await userEvent.click(screen.getByText('Test Trainer'))

      const submitButton = screen.getByRole('button', { name: /create workout/i })
      await userEvent.click(submitButton)

      // Verify moveFiles was called
      await waitFor(() => {
        expect(mockMoveFiles).toHaveBeenCalled()
      })

      // Verify update was called to update URLs
      await waitFor(() => {
        expect(mockUpdate).toHaveBeenCalled()
      })
    })

    it('should handle file movement errors', async () => {
      const consoleWarnMock = vi.spyOn(console, 'warn').mockImplementation(() => {})
      
      // Setup mock to return errors
      mockMoveFiles.mockResolvedValue({
        thumbnail: null,
        video: null,
        errors: ['Failed to move thumbnail', 'Failed to move video']
      })

      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })

      // Fill in required fields
      await userEvent.type(screen.getByLabelText(/workout title/i), 'Test Workout')
      
      // Select trainer
      await userEvent.click(screen.getByLabelText(/trainer/i))
      await userEvent.click(screen.getByText('Test Trainer'))

      const submitButton = screen.getByRole('button', { name: /create workout/i })
      await userEvent.click(submitButton)

      // Verify warning was logged
      await waitFor(() => {
        expect(consoleWarnMock).toHaveBeenCalledWith(
          'Some files could not be moved:',
          ['Failed to move thumbnail', 'Failed to move video']
        )
      })

      consoleWarnMock.mockRestore()
    })

    it('should handle submission error', async () => {
      const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => {})
      
      // Setup insert to fail
      mockInsertSelectSingle.mockResolvedValue({
        data: null,
        error: { message: 'Database error' }
      })

      render(<WorkoutForm mode="create" />)

      await waitFor(() => {
        expect(screen.getByLabelText(/workout title/i)).toBeInTheDocument()
      })

      // Fill in required fields
      await userEvent.type(screen.getByLabelText(/workout title/i), 'Test Workout')
      
      // Select trainer
      await userEvent.click(screen.getByLabelText(/trainer/i))
      await userEvent.click(screen.getByText('Test Trainer'))

      const submitButton = screen.getByRole('button', { name: /create workout/i })
      await userEvent.click(submitButton)

      // Should show alert on error
      await waitFor(() => {
        expect(alertMock).toHaveBeenCalledWith('Failed to save workout. Please try again.')
      })

      alertMock.mockRestore()
    })

    it('should handle update error in edit mode', async () => {
      const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => {})
      const consoleErrorMock = vi.spyOn(console, 'error').mockImplementation(() => {})
      
      // Setup update to fail
      mockUpdateEqSelectSingle.mockResolvedValue({
        data: null,
        error: { message: 'Update failed' }
      })

      const initialData = {
        id: 'test-id',
        title: 'Test Workout',
        trainer_id: 'trainer-1',
        category: 'full-body' as const,
        level: 'Beginner' as const,
        duration: 4,
        rounds: 8,
        prep_time: 10,
        work_time: 20,
        rest_time: 10,
        calories: 45,
        equipment: [],
        music_vibe: 'electronic' as const,
        exercises: [{ name: 'Push-ups', duration: 20 }],
        thumbnail_url: null,
        video_url: null,
        is_featured: false,
        created_at: '2024-01-01',
        updated_at: '2024-01-01',
      }

      render(<WorkoutForm mode="edit" initialData={initialData} />)

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /update workout/i })).toBeInTheDocument()
      })

      const submitButton = screen.getByRole('button', { name: /update workout/i })
      await userEvent.click(submitButton)

      // Should show alert on error
      await waitFor(() => {
        expect(alertMock).toHaveBeenCalledWith('Failed to save workout. Please try again.')
      })

      alertMock.mockRestore()
      consoleErrorMock.mockRestore()
    })
  })
})