import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'
import { ProfileForm } from '../profile-form'
import { ChangePasswordForm } from '../change-password-form'

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function changeInput(element: HTMLElement, value: string) {
  fireEvent.change(element, { target: { value } })
}

// ---------------------------------------------------------------------------
// ProfileForm
// ---------------------------------------------------------------------------

const defaultProfileProps = {
  initialValues: { firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com' },
  onSubmit: jest.fn(),
}

describe('ProfileForm', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  it('renders all fields with initial values', () => {
    render(<ProfileForm {...defaultProfileProps} />)

    expect(screen.getByLabelText('Email')).toHaveValue('jane@example.com')
    expect(screen.getByLabelText('First name')).toHaveValue('Jane')
    expect(screen.getByLabelText('Last name')).toHaveValue('Doe')
  })

  it('email field is always disabled', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    expect(screen.getByLabelText('Email')).toBeDisabled()
  })

  it('save button is disabled when no changes have been made', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    expect(screen.getByRole('button', { name: /save changes/i })).toBeDisabled()
  })

  it('save button becomes enabled after editing first name', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    changeInput(screen.getByLabelText('First name'), 'John')
    expect(screen.getByRole('button', { name: /save changes/i })).toBeEnabled()
  })

  it('save button becomes enabled after editing last name', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    changeInput(screen.getByLabelText('Last name'), 'Smith')
    expect(screen.getByRole('button', { name: /save changes/i })).toBeEnabled()
  })

  it('save button is disabled again when edits are reverted', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    changeInput(screen.getByLabelText('First name'), 'John')
    expect(screen.getByRole('button', { name: /save changes/i })).toBeEnabled()
    changeInput(screen.getByLabelText('First name'), 'Jane')
    expect(screen.getByRole('button', { name: /save changes/i })).toBeDisabled()
  })

  it('calls onSubmit with trimmed first_name and last_name', async () => {
    const onSubmit = jest.fn().mockResolvedValue(undefined)
    render(<ProfileForm {...defaultProfileProps} onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('First name'), '  John  ')
    fireEvent.submit(screen.getByRole('button', { name: /save changes/i }).closest('form')!)

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({
        first_name: 'John',
        last_name: 'Doe',
      })
    })
  })

  it('shows success message after successful submit', async () => {
    const onSubmit = jest.fn().mockResolvedValue(undefined)
    render(<ProfileForm {...defaultProfileProps} onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('First name'), 'John')
    fireEvent.submit(screen.getByRole('button', { name: /save changes/i }).closest('form')!)

    await waitFor(() => {
      expect(screen.getByText('Profile updated.')).toBeInTheDocument()
    })
  })

  it('shows error message when onSubmit throws', async () => {
    const onSubmit = jest.fn().mockRejectedValue(new Error('Server error'))
    render(<ProfileForm {...defaultProfileProps} onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('First name'), 'John')
    fireEvent.submit(screen.getByRole('button', { name: /save changes/i }).closest('form')!)

    await waitFor(() => {
      expect(screen.getByText('Server error')).toBeInTheDocument()
    })
  })

  it('shows generic error message when thrown value is not an Error', async () => {
    const onSubmit = jest.fn().mockRejectedValue('oops')
    render(<ProfileForm {...defaultProfileProps} onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('First name'), 'John')
    fireEvent.submit(screen.getByRole('button', { name: /save changes/i }).closest('form')!)

    await waitFor(() => {
      expect(screen.getByText('Failed to update profile')).toBeInTheDocument()
    })
  })

  it('disables inputs when disabled prop is true', () => {
    render(<ProfileForm {...defaultProfileProps} disabled />)
    expect(screen.getByLabelText('First name')).toBeDisabled()
    expect(screen.getByLabelText('Last name')).toBeDisabled()
  })

  it('renders card title', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    expect(screen.getByText('Profile')).toBeInTheDocument()
  })

  it('renders card description', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    expect(screen.getByText('Update your personal information.')).toBeInTheDocument()
  })

  it('renders email-cannot-be-changed hint', () => {
    render(<ProfileForm {...defaultProfileProps} />)
    expect(screen.getByText('Email cannot be changed.')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// ChangePasswordForm
// ---------------------------------------------------------------------------

const defaultPasswordProps = {
  onSubmit: jest.fn(),
}

describe('ChangePasswordForm', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  it('renders both password fields (current + new, no confirm — startsim-nbq)', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)
    expect(screen.getByLabelText('Current password')).toBeInTheDocument()
    expect(screen.getByLabelText('New password')).toBeInTheDocument()
    expect(screen.queryByLabelText('Confirm new password')).not.toBeInTheDocument()
  })

  it('submit button is disabled when fields are empty', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)
    expect(screen.getByRole('button', { name: /change password/i })).toBeDisabled()
  })

  it('submit button is disabled when new password is less than 8 characters', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)

    changeInput(screen.getByLabelText('Current password'), 'oldpassword')
    changeInput(screen.getByLabelText('New password'), 'short')

    expect(screen.getByRole('button', { name: /change password/i })).toBeDisabled()
  })

  it('shows password length hint when new password has 1-7 chars', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)
    changeInput(screen.getByLabelText('New password'), 'abc')
    expect(screen.getByText('Must be at least 8 characters.')).toBeInTheDocument()
  })

  it('hides length hint when new password is empty', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)
    // Don't type anything — hint should not appear
    expect(screen.queryByText('Must be at least 8 characters.')).not.toBeInTheDocument()
  })

  it('submit button is enabled when current + new (>=8) are filled', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)

    changeInput(screen.getByLabelText('Current password'), 'oldpassword')
    changeInput(screen.getByLabelText('New password'), 'newpassword1')

    expect(screen.getByRole('button', { name: /change password/i })).toBeEnabled()
  })

  it('calls onSubmit with { old_password, new_password } — no confirm', async () => {
    const onSubmit = jest.fn().mockResolvedValue(undefined)
    render(<ChangePasswordForm onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('Current password'), 'oldpassword')
    changeInput(screen.getByLabelText('New password'), 'newpassword1')

    fireEvent.click(screen.getByRole('button', { name: /change password/i }))

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({
        old_password: 'oldpassword',
        new_password: 'newpassword1',
      })
    })
  })

  it('shows success message and clears fields after successful submit', async () => {
    const onSubmit = jest.fn().mockResolvedValue(undefined)
    render(<ChangePasswordForm onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('Current password'), 'oldpassword')
    changeInput(screen.getByLabelText('New password'), 'newpassword1')

    fireEvent.click(screen.getByRole('button', { name: /change password/i }))

    await waitFor(() => {
      expect(screen.getByText('Password changed successfully.')).toBeInTheDocument()
    })

    expect(screen.getByLabelText('Current password')).toHaveValue('')
    expect(screen.getByLabelText('New password')).toHaveValue('')
  })

  it('calls onSuccess callback after successful submit', async () => {
    const onSubmit = jest.fn().mockResolvedValue(undefined)
    const onSuccess = jest.fn()
    render(<ChangePasswordForm onSubmit={onSubmit} onSuccess={onSuccess} />)

    changeInput(screen.getByLabelText('Current password'), 'oldpassword')
    changeInput(screen.getByLabelText('New password'), 'newpassword1')

    fireEvent.click(screen.getByRole('button', { name: /change password/i }))

    await waitFor(() => {
      expect(onSuccess).toHaveBeenCalledTimes(1)
    })
  })

  it('shows error message when onSubmit throws', async () => {
    const onSubmit = jest.fn().mockRejectedValue(new Error('Wrong password'))
    render(<ChangePasswordForm onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('Current password'), 'wrongold')
    changeInput(screen.getByLabelText('New password'), 'newpassword1')

    fireEvent.click(screen.getByRole('button', { name: /change password/i }))

    await waitFor(() => {
      expect(screen.getByText('Wrong password')).toBeInTheDocument()
    })
  })

  it('shows generic error when thrown value is not Error instance', async () => {
    const onSubmit = jest.fn().mockRejectedValue('oops')
    render(<ChangePasswordForm onSubmit={onSubmit} />)

    changeInput(screen.getByLabelText('Current password'), 'wrongold')
    changeInput(screen.getByLabelText('New password'), 'newpassword1')

    fireEvent.click(screen.getByRole('button', { name: /change password/i }))

    await waitFor(() => {
      expect(screen.getByText('Failed to change password')).toBeInTheDocument()
    })
  })

  it('disables both inputs when disabled prop is true', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} disabled />)
    expect(screen.getByLabelText('Current password')).toBeDisabled()
    expect(screen.getByLabelText('New password')).toBeDisabled()
  })

  it('renders card heading', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)
    // Use role query to specifically find the heading, not the button
    expect(screen.getByRole('heading', { name: /change password/i })).toBeInTheDocument()
  })

  it('renders card description', () => {
    render(<ChangePasswordForm {...defaultPasswordProps} />)
    expect(
      screen.getByText(/update your password/i)
    ).toBeInTheDocument()
  })
})
