import { describe, it, expect, beforeEach, vi } from 'vitest'
import { mount, VueWrapper, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import UserProfile from '../components/profile/UserProfile.vue'
import { createMemoryHistory, createRouter } from 'vue-router'

// Mock fetch globally
global.fetch = vi.fn()

// Mock router
const mockRouter = createRouter({
  history: createMemoryHistory(),
  routes: [
    { path: '/auth/login', name: 'Login', component: { template: '<div />' } },
    { path: '/profile', name: 'UserProfile', component: { template: '<div />' } },
  ],
})

// Mock auth store
const { mockLogoutUser, mockSuppressForceLogout } = vi.hoisted(() => ({
  mockLogoutUser: vi.fn(),
  mockSuppressForceLogout: { value: false },
}))
vi.mock('../stores/auth', () => ({
  logoutUser: () => mockLogoutUser(),
  suppressForceLogout: mockSuppressForceLogout,
}))

// Mock push subscription service
vi.mock('../services/pushSubscription', () => ({
  isSubscribedToPush: vi.fn().mockResolvedValue(false),
  subscribeToPushWithResult: vi.fn().mockResolvedValue({ ok: true }),
  unsubscribeFromPush: vi.fn().mockResolvedValue(undefined),
  getPushPermissionState: vi.fn().mockReturnValue('default'),
}))

const mockResetAllProgress = vi.fn().mockResolvedValue(undefined)
vi.mock('@/tutorial/controller', () => ({
  tutorialEnabled: { value: true },
  setTutorialEnabled: vi.fn(),
  resetAllProgress: () => mockResetAllProgress(),
}))

function stubModalDialog() {
  return {
    template: '<div class="mock-modal"><h2 v-if="title">{{ title }}</h2><slot /></div>',
    props: ['title'],
  }
}

function stubImagePicker() {
  return {
    template: '<div class="mock-image-picker" />',
    props: ['modelValue', 'imageType'],
    emits: ['update:modelValue', 'add-image'],
  }
}

function stubToggleField() {
  return {
    template: '<div class="mock-toggle-field" />',
    props: ['label', 'modelValue', 'disabled', 'description', 'error'],
    emits: ['update:modelValue'],
  }
}

function stubProfileSection() {
  return {
    template: '<div class="mock-profile-section"><slot /></div>',
    props: ['title', 'defaultOpen'],
  }
}

describe('UserProfile - Delete Account', () => {
  let wrapper: VueWrapper<any>

  beforeEach(() => {
    vi.clearAllMocks()
    ;(global.fetch as any).mockClear()

    // Mock fetch for profile loading in onMounted
    ;(global.fetch as any).mockResolvedValue({
      ok: true,
      json: async () => ({
        image_id: null,
        first_name: 'Test',
        last_name: 'User',
        email: 'test@example.com',
      }),
    })

    // Mount component with router
    wrapper = mount(UserProfile, {
      global: {
        plugins: [mockRouter],
        stubs: {
          ProfileSection: stubProfileSection(),
          ImagePicker: stubImagePicker(),
          ToggleField: stubToggleField(),
          ModalDialog: stubModalDialog(),
        },
      },
    })
  })

  it('renders Delete My Account button', async () => {
    await flushPromises()
    await nextTick()

    expect(wrapper.vm.openDeleteWarning).toBeDefined()
    expect(wrapper.vm.confirmDeleteAccount).toBeDefined()
  })

  it('opens warning modal when Delete My Account button is clicked', async () => {
    wrapper.vm.openDeleteWarning()
    await nextTick()

    expect(wrapper.vm.showDeleteWarning).toBe(true)
    expect(wrapper.vm.confirmEmail).toBe('')
  })

  it('Delete button in warning modal is disabled until email matches', async () => {
    // Set initial email
    wrapper.vm.email = 'test@example.com'

    // Open warning modal
    await wrapper.vm.openDeleteWarning()
    await nextTick()

    wrapper.vm.confirmEmail = 'wrong@example.com'
    await nextTick()
    expect(wrapper.vm.confirmEmail).not.toBe(wrapper.vm.email)

    wrapper.vm.confirmEmail = 'test@example.com'
    await nextTick()
    expect(wrapper.vm.confirmEmail).toBe(wrapper.vm.email)
  })

  it('calls API when confirmed with correct email', async () => {
    const mockResponse = {
      ok: true,
      json: async () => ({ success: true }),
    }
    ;(global.fetch as any).mockResolvedValueOnce(mockResponse)

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(global.fetch).toHaveBeenCalledWith(
      '/api/user/mark-for-deletion',
      expect.objectContaining({
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: 'test@example.com' }),
      }),
    )
  })

  it('does not call API if email is invalid format', async () => {
    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'invalid-email'

    await wrapper.vm.confirmDeleteAccount()

    // Only the profile fetch should have been called, not mark-for-deletion
    expect(global.fetch).toHaveBeenCalledTimes(1)
    expect(global.fetch).toHaveBeenCalledWith('/api/user/profile')
  })

  it('shows success modal after successful API response', async () => {
    const mockResponse = {
      ok: true,
      json: async () => ({ success: true }),
    }
    ;(global.fetch as any).mockResolvedValueOnce(mockResponse)

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(wrapper.vm.showDeleteWarning).toBe(false)
    expect(wrapper.vm.showDeleteSuccess).toBe(true)
  })

  it('sets suppressForceLogout before making the API call', async () => {
    let flagDuringRequest = false
    ;(global.fetch as any).mockImplementationOnce(async () => {
      flagDuringRequest = mockSuppressForceLogout.value
      return { ok: true, json: async () => ({ success: true }) }
    })

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()

    expect(flagDuringRequest).toBe(true)
  })

  it('leaves suppressForceLogout set after successful deletion', async () => {
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => ({ success: true }),
    })

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(mockSuppressForceLogout.value).toBe(true)
  })

  it('clears suppressForceLogout on API failure', async () => {
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: false,
      status: 400,
      json: async () => ({ error: 'fail', code: 'ERROR' }),
    })

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(mockSuppressForceLogout.value).toBe(false)
  })

  it('clears suppressForceLogout on network error', async () => {
    ;(global.fetch as any).mockRejectedValueOnce(new Error('Network error'))

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(mockSuppressForceLogout.value).toBe(false)
  })

  it('shows error modal on API failure', async () => {
    const mockResponse = {
      ok: false,
      status: 400,
      json: async () => ({
        error: 'Account already marked',
        code: 'ALREADY_MARKED',
      }),
    }
    ;(global.fetch as any).mockResolvedValueOnce(mockResponse)

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(wrapper.vm.showDeleteWarning).toBe(false)
    expect(wrapper.vm.showDeleteError).toBe(true)
    expect(wrapper.vm.deleteErrorMessage).toBe('This account is already marked for deletion.')
  })

  it('shows error modal on network error', async () => {
    ;(global.fetch as any).mockRejectedValueOnce(new Error('Network error'))

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(wrapper.vm.showDeleteWarning).toBe(false)
    expect(wrapper.vm.showDeleteError).toBe(true)
    expect(wrapper.vm.deleteErrorMessage).toBe('Network error. Please try again.')
  })

  it('signs out user after success modal OK button', async () => {
    wrapper.vm.showDeleteSuccess = true

    await wrapper.vm.handleDeleteSuccess()
    await nextTick()

    expect(wrapper.vm.showDeleteSuccess).toBe(false)
    expect(mockLogoutUser).toHaveBeenCalled()
  })

  it('redirects to landing page after sign-out', async () => {
    const pushSpy = vi.spyOn(mockRouter, 'push')

    wrapper.vm.showDeleteSuccess = true
    await wrapper.vm.handleDeleteSuccess()
    await nextTick()

    expect(pushSpy).toHaveBeenCalledWith('/')
  })

  it('closes error modal when Close button is clicked', async () => {
    wrapper.vm.showDeleteError = true
    wrapper.vm.deleteErrorMessage = 'Some error'

    await wrapper.vm.closeDeleteError()
    await nextTick()

    expect(wrapper.vm.showDeleteError).toBe(false)
    expect(wrapper.vm.deleteErrorMessage).toBe('')
  })

  it('closes warning modal when cancelled', async () => {
    wrapper.vm.showDeleteWarning = true
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.closeDeleteWarning()
    await nextTick()

    expect(wrapper.vm.showDeleteWarning).toBe(false)
    expect(wrapper.vm.confirmEmail).toBe('')
  })

  it('disables button during loading', async () => {
    const mockResponse = {
      ok: true,
      json: async () => ({ success: true }),
    }
    ;(global.fetch as any).mockImplementation(
      () =>
        new Promise((resolve) => {
          setTimeout(() => resolve(mockResponse), 100)
        }),
    )

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    const deletePromise = wrapper.vm.confirmDeleteAccount()
    await nextTick()

    // Check loading state is true during API call
    expect(wrapper.vm.deletingAccount).toBe(true)

    await deletePromise
    await nextTick()

    // Check loading state is false after API call
    expect(wrapper.vm.deletingAccount).toBe(false)
  })

  it('resets confirmEmail when opening warning modal', async () => {
    wrapper.vm.confirmEmail = 'old@example.com'

    await wrapper.vm.openDeleteWarning()
    await nextTick()

    expect(wrapper.vm.confirmEmail).toBe('')
    expect(wrapper.vm.showDeleteWarning).toBe(true)
  })

  it('handles ALREADY_MARKED error code correctly', async () => {
    const mockResponse = {
      ok: false,
      status: 400,
      json: async () => ({
        error: 'Already marked',
        code: 'ALREADY_MARKED',
      }),
    }
    ;(global.fetch as any).mockResolvedValueOnce(mockResponse)

    wrapper.vm.email = 'test@example.com'
    wrapper.vm.confirmEmail = 'test@example.com'

    await wrapper.vm.confirmDeleteAccount()
    await nextTick()

    expect(wrapper.vm.deleteErrorMessage).toBe('This account is already marked for deletion.')
  })
})

describe('UserProfile - Auto-save', () => {
  let wrapper: VueWrapper<any>

  beforeEach(() => {
    vi.clearAllMocks()
    ;(global.fetch as any).mockClear()

    // Mock fetch for profile loading in onMounted
    ;(global.fetch as any).mockResolvedValue({
      ok: true,
      json: async () => ({
        image_id: 'initial-image-id',
        first_name: 'Test',
        last_name: 'User',
        email: 'test@example.com',
        email_digest_enabled: true,
      }),
    })

    wrapper = mount(UserProfile, {
      global: {
        plugins: [mockRouter],
        stubs: {
          ProfileSection: stubProfileSection(),
          ImagePicker: stubImagePicker(),
          ToggleField: stubToggleField(),
          ModalDialog: stubModalDialog(),
        },
      },
    })
  })

  it('saveNames sends PUT with first and last name', async () => {
    await flushPromises()
    await nextTick()
    ;(global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => ({}) })

    wrapper.vm.firstName = 'Updated'
    wrapper.vm.lastName = 'Name'
    await wrapper.vm.saveNames()
    await flushPromises()

    const putCall = (global.fetch as any).mock.calls.find((c: any[]) => c[1]?.method === 'PUT')
    expect(putCall).toBeDefined()
    const body = JSON.parse(putCall[1].body)
    expect(body.first_name).toBe('Updated')
    expect(body.last_name).toBe('Name')
  })

  it('saveImage sends PUT with image_id', async () => {
    await flushPromises()
    await nextTick()
    ;(global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => ({}) })

    await wrapper.vm.saveImage('new-image-id')
    await flushPromises()

    const putCall = (global.fetch as any).mock.calls.find((c: any[]) => c[1]?.method === 'PUT')
    expect(putCall).toBeDefined()
    const body = JSON.parse(putCall[1].body)
    expect(body.image_id).toBe('new-image-id')
  })

  it('uploadLocalImage uploads file then saves image_id', async () => {
    await flushPromises()
    await nextTick()

    const mockFile = new File(['test'], 'test.png', { type: 'image/png' })
    wrapper.vm.localImageFile = mockFile

    // Mock image upload response
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => ({ id: 'uploaded-image-id' }),
    })

    // Mock profile update response
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => ({}),
    })

    await wrapper.vm.uploadLocalImage()
    await flushPromises()

    // Should have called image upload
    const uploadCall = (global.fetch as any).mock.calls.find(
      (c: any[]) => c[0] === '/api/image/upload',
    )
    expect(uploadCall).toBeDefined()
    expect(uploadCall[1].method).toBe('POST')

    // imageId should be updated
    expect(wrapper.vm.imageId).toBe('uploaded-image-id')
  })

  it('shows error message on failed image upload', async () => {
    await flushPromises()
    await nextTick()

    const mockFile = new File(['test'], 'test.png', { type: 'image/png' })
    wrapper.vm.localImageFile = mockFile

    // Mock failed image upload
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: false,
      status: 500,
    })

    await wrapper.vm.uploadLocalImage()
    await flushPromises()

    expect(wrapper.vm.errorMsg).toBe('Failed to upload image.')
  })

  it('shows error message on failed profile update', async () => {
    await flushPromises()
    await nextTick()
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: false,
      status: 500,
    })

    await wrapper.vm.saveNames()
    await flushPromises()

    expect(wrapper.vm.errorMsg).toBe('Failed to update profile.')
  })
})

describe('UserProfile - Notification Toggles', () => {
  let wrapper: VueWrapper<any>

  function mountWithDigest(emailDigestEnabled: boolean) {
    ;(global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => ({
        image_id: null,
        first_name: 'Test',
        last_name: 'User',
        email: 'test@example.com',
        email_digest_enabled: emailDigestEnabled,
      }),
    })
    return mount(UserProfile, {
      global: {
        plugins: [mockRouter],
        stubs: {
          ProfileSection: stubProfileSection(),
          ImagePicker: stubImagePicker(),
          ToggleField: stubToggleField(),
          ModalDialog: stubModalDialog(),
        },
      },
    })
  }

  beforeEach(() => {
    vi.clearAllMocks()
    ;(global.fetch as any).mockClear()
    ;(global.fetch as any).mockResolvedValue({
      ok: true,
      json: async () => ({}),
    })
  })

  it('initializes emailDigestEnabled to true when profile returns true', async () => {
    wrapper = mountWithDigest(true)
    await flushPromises()
    await nextTick()

    expect(wrapper.vm.emailDigestEnabled).toBe(true)
  })

  it('initializes emailDigestEnabled to false when profile returns false', async () => {
    wrapper = mountWithDigest(false)
    await flushPromises()
    await nextTick()

    expect(wrapper.vm.emailDigestEnabled).toBe(false)
  })

  it('initializes pushEnabled to false when not subscribed', async () => {
    wrapper = mountWithDigest(true)
    await flushPromises()
    await nextTick()

    expect(wrapper.vm.pushEnabled).toBe(false)
  })

  it('onToggleDigest sends PUT with new value', async () => {
    wrapper = mountWithDigest(true)
    await flushPromises()
    await nextTick()
    ;(global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => ({}) })

    await wrapper.vm.onToggleDigest(false)
    await flushPromises()

    const putCall = (global.fetch as any).mock.calls.find((c: any[]) => c[1]?.method === 'PUT')
    expect(putCall).toBeDefined()
    const body = JSON.parse(putCall[1].body)
    expect(body.email_digest_enabled).toBe(false)
  })

  it('onTogglePush sends PUT with new value and applies push change', async () => {
    wrapper = mountWithDigest(true)
    await flushPromises()
    await nextTick()
    ;(global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => ({}) })

    expect(wrapper.vm.pushEnabled).toBe(false)
    await wrapper.vm.onTogglePush(true)
    await flushPromises()

    const putCall = (global.fetch as any).mock.calls.find((c: any[]) => c[1]?.method === 'PUT')
    expect(putCall).toBeDefined()
    const body = JSON.parse(putCall[1].body)
    expect(body.push_notifications_enabled).toBe(true)
  })
})

describe('UserProfile - Tutorial restart', () => {
  let wrapper: VueWrapper<any>

  beforeEach(() => {
    vi.clearAllMocks()
    mockResetAllProgress.mockClear()
    ;(global.fetch as any).mockClear()
    ;(global.fetch as any).mockResolvedValue({
      ok: true,
      json: async () => ({
        image_id: null,
        first_name: 'Test',
        last_name: 'User',
        email: 'test@example.com',
        email_digest_enabled: true,
      }),
    })

    wrapper = mount(UserProfile, {
      global: {
        plugins: [mockRouter],
        stubs: {
          ProfileSection: stubProfileSection(),
          ImagePicker: stubImagePicker(),
          ToggleField: stubToggleField(),
          ModalDialog: stubModalDialog(),
        },
      },
    })
  })

  it('restart confirm modal uses "Tutorial Restart" title and explanatory wording', async () => {
    await flushPromises()
    await nextTick()

    await wrapper.vm.openRestartConfirm()
    await nextTick()

    const modal = wrapper.find('.mock-modal')
    expect(modal.exists()).toBe(true)
    expect(modal.text()).toContain('Tutorial Restart')
    expect(modal.text()).toContain('Start the tour again')
  })

  it('confirming restart resets tutorial progress and shows success modal', async () => {
    await flushPromises()
    await nextTick()

    await wrapper.vm.openRestartConfirm()
    await wrapper.vm.confirmRestartTutorial()
    await flushPromises()
    await nextTick()

    expect(mockResetAllProgress).toHaveBeenCalledTimes(1)

    const modal = wrapper.find('.mock-modal')
    expect(modal.text()).toContain('Tutorial Restart')
    expect(modal.text()).toContain('Tutorial mode has been restarted.')
  })
})
