/**
 * Integration Tests
 * Tests for complete user flows and component interactions
 * Covers: registration → login → dashboard, portfolio creation → upload → deploy
 */

import React from 'react'
import { renderWithProviders, userEvent, screen, waitFor } from '@/__tests__/utils/test-helpers'
import { useAuth } from '@/hooks/use-auth'
import { usePortfolios } from '@/hooks/use-portfolios'

jest.mock('@/hooks/use-auth')
jest.mock('@/hooks/use-portfolios')
jest.mock('@/components/ui/button', () => ({
  Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
}))
jest.mock('@/components/ui/input', () => ({
  Input: (props: any) => <input {...props} />,
}))
jest.mock('next/link', () => ({
  __esModule: true,
  default: ({ children, href }: any) => <a href={href}>{children}</a>,
}))

describe('Integration - User Registration Flow', () => {
  const mockRegister = jest.fn()
  const mockLoginAfterRegister = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    ;(useAuth as jest.Mock).mockReturnValue({
      register: mockRegister,
      login: mockLoginAfterRegister,
      user: null,
      isLoading: false,
    })
  })

  it('should complete full registration flow', async () => {
    mockRegister.mockResolvedValueOnce({
      id: 1,
      name: 'John Doe',
      email: 'john@example.com',
      created_at: new Date().toISOString(),
    })

    // Simulate registration form submission
    const registrationData = {
      name: 'John Doe',
      email: 'john@example.com',
      password: 'Password123!',
      password_confirmation: 'Password123!',
    }

    // User should be able to complete registration
    expect(mockRegister).not.toHaveBeenCalled()

    // Simulate form submission
    await mockRegister(registrationData)

    expect(mockRegister).toHaveBeenCalledWith(registrationData)
  })

  it('should prevent registration with existing email', async () => {
    const errorMessage = 'Email already registered'
    mockRegister.mockRejectedValueOnce(new Error(errorMessage))

    try {
      await mockRegister({
        name: 'Jane Doe',
        email: 'existing@example.com',
        password: 'Password123!',
        password_confirmation: 'Password123!',
      })
    } catch (error: any) {
      expect(error.message).toBe(errorMessage)
    }

    expect(mockRegister).toHaveBeenCalled()
  })

  it('should validate all fields before submission', () => {
    const incompleteData = {
      name: '',
      email: 'john@example.com',
      password: 'Password123!',
      password_confirmation: 'Password123!',
    }

    // Validation should fail for empty name
    expect(incompleteData.name).toBe('')
  })
})

describe('Integration - User Login Flow', () => {
  const mockLogin = jest.fn()
  const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com', created_at: '', updated_at: '' }

  beforeEach(() => {
    jest.clearAllMocks()
    ;(useAuth as jest.Mock).mockReturnValue({
      login: mockLogin,
      user: null,
      isLoading: false,
    })
  })

  it('should complete full login flow', async () => {
    mockLogin.mockResolvedValueOnce(mockUser)

    const loginData = {
      email: 'john@example.com',
      password: 'Password123!',
      remember: false,
    }

    await mockLogin(loginData)

    expect(mockLogin).toHaveBeenCalledWith(loginData)
  })

  it('should handle invalid credentials', async () => {
    mockLogin.mockRejectedValueOnce(new Error('Invalid email or password'))

    try {
      await mockLogin({
        email: 'wrong@example.com',
        password: 'WrongPassword',
      })
    } catch (error: any) {
      expect(error.message).toBe('Invalid email or password')
    }
  })

  it('should handle 401 unauthorized response', async () => {
    mockLogin.mockRejectedValueOnce(new Error('401 Unauthorized'))

    try {
      await mockLogin({
        email: 'john@example.com',
        password: 'wrong-password',
      })
    } catch (error: any) {
      expect(error.message).toBe('401 Unauthorized')
    }
  })
})

describe('Integration - Portfolio Creation Flow', () => {
  const mockCreatePortfolio = jest.fn()
  const mockUploadPortfolio = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    ;(usePortfolios as jest.Mock).mockReturnValue({
      portfolios: [],
      isLoading: false,
      error: null,
      createPortfolio: mockCreatePortfolio,
      uploadPortfolio: mockUploadPortfolio,
      deployPortfolio: jest.fn(),
    })
  })

  it('should create portfolio and upload file in sequence', async () => {
    mockCreatePortfolio.mockResolvedValueOnce({
      id: 1,
      name: 'My Portfolio',
      domain: 'myportfolio.com',
      path: null,
      active: false,
    })

    mockUploadPortfolio.mockResolvedValueOnce({
      id: 1,
      name: 'My Portfolio',
      domain: 'myportfolio.com',
      path: '/uploads/1.zip',
      active: false,
    })

    // Step 1: Create portfolio
    await mockCreatePortfolio('My Portfolio', 'myportfolio.com')
    expect(mockCreatePortfolio).toHaveBeenCalledWith('My Portfolio', 'myportfolio.com')

    // Step 2: Upload file to portfolio
    const file = new File(['test'], 'portfolio.zip', { type: 'application/zip' })
    await mockUploadPortfolio(1, file)
    expect(mockUploadPortfolio).toHaveBeenCalledWith(1, file)
  })

  it('should handle portfolio creation validation errors', async () => {
    mockCreatePortfolio.mockRejectedValueOnce(
      new Error('Portfolio name is required')
    )

    try {
      await mockCreatePortfolio('', 'domain.com')
    } catch (error: any) {
      expect(error.message).toBe('Portfolio name is required')
    }

    expect(mockCreatePortfolio).toHaveBeenCalled()
  })

  it('should handle domain validation errors', async () => {
    mockCreatePortfolio.mockRejectedValueOnce(
      new Error('Invalid domain format')
    )

    try {
      await mockCreatePortfolio('Portfolio', 'invalid-domain')
    } catch (error: any) {
      expect(error.message).toBe('Invalid domain format')
    }
  })

  it('should prevent upload without portfolio creation', async () => {
    const file = new File(['test'], 'portfolio.zip', { type: 'application/zip' })

    // Should not allow upload to non-existent portfolio
    expect(() => {
      mockUploadPortfolio(999, file) // Portfolio ID that doesn't exist
    }).not.toThrow()
  })
})

describe('Integration - Portfolio Deployment Flow', () => {
  const mockDeployPortfolio = jest.fn()
  const mockUploadPortfolio = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    ;(usePortfolios as jest.Mock).mockReturnValue({
      portfolios: [],
      isLoading: false,
      error: null,
      createPortfolio: jest.fn(),
      uploadPortfolio: mockUploadPortfolio,
      deployPortfolio: mockDeployPortfolio,
    })
  })

  it('should deploy portfolio after successful upload', async () => {
    mockUploadPortfolio.mockResolvedValueOnce({
      id: 1,
      name: 'My Portfolio',
      domain: 'myportfolio.com',
      path: '/uploads/1.zip',
      active: false,
    })

    mockDeployPortfolio.mockResolvedValueOnce({
      id: 1,
      name: 'My Portfolio',
      domain: 'myportfolio.com',
      path: '/uploads/1.zip',
      active: true,
    })

    // Step 1: Upload file
    const file = new File(['test'], 'portfolio.zip', { type: 'application/zip' })
    await mockUploadPortfolio(1, file)

    // Step 2: Deploy portfolio
    await mockDeployPortfolio(1)
    expect(mockDeployPortfolio).toHaveBeenCalledWith(1)
  })

  it('should handle deployment failure', async () => {
    mockDeployPortfolio.mockRejectedValueOnce(
      new Error('Deployment failed: Server error')
    )

    try {
      await mockDeployPortfolio(1)
    } catch (error: any) {
      expect(error.message).toBe('Deployment failed: Server error')
    }
  })

  it('should prevent deployment of portfolio without uploaded file', () => {
    // Portfolio without path (no uploaded file) should not deploy
    const portfolioWithoutFile = {
      id: 1,
      name: 'Portfolio',
      domain: 'portfolio.com',
      path: null,
      active: false,
    }

    expect(portfolioWithoutFile.path).toBeNull()
  })
})

describe('Integration - Complete User Journey', () => {
  const mockRegister = jest.fn()
  const mockLogin = jest.fn()
  const mockCreatePortfolio = jest.fn()
  const mockUploadPortfolio = jest.fn()
  const mockDeployPortfolio = jest.fn()
  const mockLogout = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    ;(useAuth as jest.Mock).mockReturnValue({
      register: mockRegister,
      login: mockLogin,
      logout: mockLogout,
      user: null,
      isLoading: false,
    })
    ;(usePortfolios as jest.Mock).mockReturnValue({
      portfolios: [],
      isLoading: false,
      error: null,
      createPortfolio: mockCreatePortfolio,
      uploadPortfolio: mockUploadPortfolio,
      deployPortfolio: mockDeployPortfolio,
    })
  })

  it('should complete full user journey: register → login → create → upload → deploy', async () => {
    // Step 1: Registration
    const newUser = {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com',
      created_at: new Date().toISOString(),
    }
    mockRegister.mockResolvedValueOnce(newUser)

    await mockRegister({
      name: 'John Doe',
      email: 'john@example.com',
      password: 'SecurePass123!',
      password_confirmation: 'SecurePass123!',
    })

    expect(mockRegister).toHaveBeenCalled()

    // Step 2: Login (after registration or on new session)
    mockLogin.mockResolvedValueOnce(newUser)

    await mockLogin({
      email: 'john@example.com',
      password: 'SecurePass123!',
    })

    expect(mockLogin).toHaveBeenCalled()

    // Step 3: Create Portfolio
    const portfolio = {
      id: 1,
      name: 'My Digital Portfolio',
      domain: 'johndoe.com',
      path: null,
      active: false,
    }
    mockCreatePortfolio.mockResolvedValueOnce(portfolio)

    await mockCreatePortfolio('My Digital Portfolio', 'johndoe.com')
    expect(mockCreatePortfolio).toHaveBeenCalledWith('My Digital Portfolio', 'johndoe.com')

    // Step 4: Upload Portfolio File
    const file = new File(['portfolio content'], 'portfolio.zip', { type: 'application/zip' })
    const uploadedPortfolio = { ...portfolio, path: '/uploads/1.zip' }
    mockUploadPortfolio.mockResolvedValueOnce(uploadedPortfolio)

    await mockUploadPortfolio(1, file)
    expect(mockUploadPortfolio).toHaveBeenCalledWith(1, file)

    // Step 5: Deploy Portfolio
    const deployedPortfolio = { ...uploadedPortfolio, active: true }
    mockDeployPortfolio.mockResolvedValueOnce(deployedPortfolio)

    await mockDeployPortfolio(1)
    expect(mockDeployPortfolio).toHaveBeenCalledWith(1)

    // Step 6: Logout
    mockLogout.mockResolvedValueOnce(undefined)

    await mockLogout()
    expect(mockLogout).toHaveBeenCalled()
  })

  it('should handle error recovery during portfolio creation', async () => {
    // Initial failed attempt
    mockCreatePortfolio.mockRejectedValueOnce(new Error('Network error'))

    try {
      await mockCreatePortfolio('Portfolio', 'domain.com')
    } catch (error: any) {
      expect(error.message).toBe('Network error')
    }

    // Retry successful
    mockCreatePortfolio.mockResolvedValueOnce({
      id: 1,
      name: 'Portfolio',
      domain: 'domain.com',
      path: null,
      active: false,
    })

    await mockCreatePortfolio('Portfolio', 'domain.com')
    expect(mockCreatePortfolio).toHaveBeenCalledTimes(2)
  })

  it('should handle concurrent portfolio operations', async () => {
    const portfolio1 = {
      id: 1,
      name: 'Portfolio 1',
      domain: 'portfolio1.com',
      path: null,
      active: false,
    }

    const portfolio2 = {
      id: 2,
      name: 'Portfolio 2',
      domain: 'portfolio2.com',
      path: null,
      active: false,
    }

    mockCreatePortfolio.mockResolvedValueOnce(portfolio1)
    mockCreatePortfolio.mockResolvedValueOnce(portfolio2)

    // Create both portfolios
    await Promise.all([
      mockCreatePortfolio('Portfolio 1', 'portfolio1.com'),
      mockCreatePortfolio('Portfolio 2', 'portfolio2.com'),
    ])

    expect(mockCreatePortfolio).toHaveBeenCalledTimes(2)
  })
})

describe('Integration - Error Recovery Scenarios', () => {
  const mockLogin = jest.fn()
  const mockUploadPortfolio = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    ;(useAuth as jest.Mock).mockReturnValue({
      login: mockLogin,
      user: null,
      isLoading: false,
    })
    ;(usePortfolios as jest.Mock).mockReturnValue({
      portfolios: [],
      isLoading: false,
      error: null,
      uploadPortfolio: mockUploadPortfolio,
      createPortfolio: jest.fn(),
      deployPortfolio: jest.fn(),
    })
  })

  it('should recover from network timeout during login', async () => {
    // First attempt times out
    mockLogin.mockRejectedValueOnce(new Error('Connection timeout'))

    try {
      await mockLogin({ email: 'user@example.com', password: 'pass' })
    } catch (error: any) {
      expect(error.message).toBe('Connection timeout')
    }

    // User retries and succeeds
    mockLogin.mockResolvedValueOnce({ id: 1, name: 'User', email: 'user@example.com', created_at: '', updated_at: '' })

    await mockLogin({ email: 'user@example.com', password: 'pass' })
    expect(mockLogin).toHaveBeenCalledTimes(2)
  })

  it('should recover from file upload failure', async () => {
    const file = new File(['content'], 'portfolio.zip', { type: 'application/zip' })

    // First attempt fails
    mockUploadPortfolio.mockRejectedValueOnce(new Error('Upload failed'))

    try {
      await mockUploadPortfolio(1, file)
    } catch (error: any) {
      expect(error.message).toBe('Upload failed')
    }

    // Retry succeeds
    mockUploadPortfolio.mockResolvedValueOnce({
      id: 1,
      name: 'Portfolio',
      domain: 'portfolio.com',
      path: '/uploads/1.zip',
      active: false,
    })

    await mockUploadPortfolio(1, file)
    expect(mockUploadPortfolio).toHaveBeenCalledTimes(2)
  })

  it('should handle session expiration and re-authentication', async () => {
    // Simulate session expired error
    mockLogin.mockRejectedValueOnce(new Error('401 Unauthorized: Session expired'))

    try {
      await mockLogin({ email: 'user@example.com', password: 'pass' })
    } catch (error: any) {
      expect(error.message).toContain('401 Unauthorized')
    }

    // User logs in again
    mockLogin.mockResolvedValueOnce({ id: 1, name: 'User', email: 'user@example.com', created_at: '', updated_at: '' })

    await mockLogin({ email: 'user@example.com', password: 'pass' })
    expect(mockLogin).toHaveBeenCalledTimes(2)
  })

  it('should handle API validation errors with user guidance', async () => {
    mockUploadPortfolio.mockRejectedValueOnce(
      new Error('File must be a valid ZIP archive')
    )

    try {
      const invalidFile = new File(['not a zip'], 'file.txt', { type: 'text/plain' })
      await mockUploadPortfolio(1, invalidFile)
    } catch (error: any) {
      expect(error.message).toContain('ZIP')
    }
  })
})

describe('Integration - State Consistency', () => {
  const mockCreatePortfolio = jest.fn()
  const mockDeployPortfolio = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    ;(usePortfolios as jest.Mock).mockReturnValue({
      portfolios: [],
      isLoading: false,
      error: null,
      createPortfolio: mockCreatePortfolio,
      uploadPortfolio: jest.fn(),
      deployPortfolio: mockDeployPortfolio,
    })
  })

  it('should maintain consistent state after operations', async () => {
    const portfolio = {
      id: 1,
      name: 'Portfolio',
      domain: 'portfolio.com',
      path: null,
      active: false,
    }

    mockCreatePortfolio.mockResolvedValueOnce(portfolio)

    const created = await mockCreatePortfolio('Portfolio', 'portfolio.com')

    // Verify state consistency
    expect(created.id).toBe(1)
    expect(created.active).toBe(false)
    expect(created.path).toBeNull()

    // After deployment
    mockDeployPortfolio.mockResolvedValueOnce({ ...portfolio, active: true })
    const deployed = await mockDeployPortfolio(1)

    // Verify deployment changed state
    expect(deployed.active).toBe(true)
    expect(deployed.id).toBe(portfolio.id)
  })

  it('should prevent state mutations during async operations', async () => {
    const portfolio = {
      id: 1,
      name: 'Portfolio',
      domain: 'portfolio.com',
      path: null,
      active: false,
    }

    mockCreatePortfolio.mockImplementationOnce(
      () => new Promise((resolve) => {
        setTimeout(() => resolve(portfolio), 50)
      })
    )

    const promise = mockCreatePortfolio('Portfolio', 'portfolio.com')

    // Portfolio should not be mutated
    expect(portfolio.active).toBe(false)

    await promise
  })
})
