// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

import * as React from 'react'
import RunningSessions from '../../components/RunningSessions/RunningSessions'
import SessionInfo from '../../models/session-info'
import { act, screen, within, waitFor } from '@testing-library/react'
import { render } from '../utils/render-utils'
import userEvent from '@testing-library/user-event'
import { createSessionData } from '../../models/session-data'

global.fetch = jest.fn()

Object.defineProperty(window, 'location', {
  value: {
    origin: 'http://localhost:4444/selenium',
    href: 'http://localhost:4444/selenium/ui/#/sessions'
  },
  writable: true
})

const origin = 'http://localhost:4444/selenium'

const sessionsInfo: SessionInfo[] = [
  {
    id: 'aee43d1c1d10e85d359029719c20b146',
    capabilities: '{ "browserName": "chrome", "browserVersion": "88.0.4324.182", "platformName": "windows" }',
    startTime: '18/02/2021 13:12:05',
    uri: 'http://192.168.1.7:4444',
    nodeId: '9fe799f4-4397-4fbb-9344-1d5a3074695e',
    nodeUri: 'http://192.168.1.7:5555',
    sessionDurationMillis: '123456',
    slot: {
      id: '3c1e1508-c548-48fb-8a99-4332f244d87b',
      stereotype: '{"browserName": "chrome"}',
      lastStarted: '18/02/2021 13:12:05'
    }
  },
  {
    id: 'yhVTTv2iHuqMB3chxkfDBLqlzeyORnvf',
    capabilities: '{ "browserName": "edge", "browserVersion": "96.0.1054.72", "platformName": "windows" }',
    startTime: '18/02/2021 13:13:05',
    uri: 'http://192.168.3.7:4444',
    nodeId: 'h9x799f4-4397-4fbb-9344-1d5a3074695e',
    nodeUri: 'http://192.168.1.3:5555',
    sessionDurationMillis: '123456',
    slot: {
      id: '5070c2eb-8094-4692-8911-14c533619f7d',
      stereotype: '{"browserName": "edge"}',
      lastStarted: '18/02/2021 13:13:05'
    }
  },
  {
    id: 'p1s201AORfsFN11r1JB1Ycd9ygyRdCin',
    capabilities: '{ "browserName": "firefox", "browserVersion": "103.0", "platformName": "windows", "se:random_cap": "test_func" }',
    startTime: '18/02/2021 13:15:05',
    uri: 'http://192.168.4.7:4444',
    nodeId: 'h9x799f4-4397-4fbb-9344-1d5a3074695e',
    nodeUri: 'http://192.168.1.3:5555',
    sessionDurationMillis: '123456',
    slot: {
      id: 'ae48d687-610b-472d-9e0c-3ebc28ad7211',
      stereotype: '{"browserName": "firefox"}',
      lastStarted: '18/02/2021 13:15:05'
    }
  }
]

const sessionWithWebSocketUrl: SessionInfo = {
  id: '2103faaea8600e41a1e86f4189779e66',
  capabilities: JSON.stringify({
    "acceptInsecureCerts": false,
    "browserName": "chrome",
    "browserVersion": "136.0.7103.113",
    "chrome": {
      "chromedriverVersion": "136.0.7103.113 (76fa3c1782406c63308c70b54f228fd39c7aaa71-refs/branch-heads/7103_108@{#3})",
      "userDataDir": "/tmp/.org.chromium.Chromium.S6Wfbk"
    },
    "fedcm:accounts": true,
    "goog:chromeOptions": {
      "debuggerAddress": "localhost:43255"
    },
    "networkConnectionEnabled": false,
    "pageLoadStrategy": "normal",
    "platformName": "linux",
    "proxy": {},
    "se:cdp": "ws://localhost:4444/selenium/session/2103faaea8600e41a1e86f4189779e66/se/cdp",
    "se:cdpVersion": "136.0.7103.113",
    "se:containerName": "0ca4ada66da5",
    "se:deleteSessionOnUi": true,
    "se:downloadsEnabled": true,
    "se:gridWebSocketUrl": "ws://localhost:4444/selenium/session/2103faaea8600e41a1e86f4189779e66",
    "se:noVncPort": 7900,
    "se:vnc": "ws://localhost:4444/selenium/session/2103faaea8600e41a1e86f4189779e66/se/vnc",
    "se:vncEnabled": true,
    "se:vncLocalAddress": "ws://172.18.0.7:7900",
    "setWindowRect": true,
    "strictFileInteractability": false,
    "timeouts": {
      "implicit": 0,
      "pageLoad": 300000,
      "script": 30000
    },
    "unhandledPromptBehavior": "dismiss and notify",
    "webSocketUrl": "ws://localhost:4444/selenium/session/2103faaea8600e41a1e86f4189779e66/se/bidi",
    "webauthn:extension:credBlob": true,
    "webauthn:extension:largeBlob": true,
    "webauthn:extension:minPinLength": true,
    "webauthn:extension:prf": true,
    "webauthn:virtualAuthenticators": true
  }),
  startTime: '27/05/2025 13:12:05',
  uri: 'http://localhost:4444',
  nodeId: '9fe799f4-4397-4fbb-9344-1d5a3074695e',
  nodeUri: 'http://localhost:5555',
  sessionDurationMillis: '123456',
  slot: {
    id: '3c1e1508-c548-48fb-8a99-4332f244d87b',
    stereotype: '{"browserName": "chrome"}',
    lastStarted: '27/05/2025 13:12:05'
  }
}

const sessionWithoutWebSocketUrl: SessionInfo = {
  id: 'aee43d1c1d10e85d359029719c20b146',
  capabilities: JSON.stringify({
    "browserName": "chrome",
    "browserVersion": "88.0.4324.182",
    "platformName": "windows",
    "se:deleteSessionOnUi": true
  }),
  startTime: '27/05/2025 13:13:05',
  uri: 'http://localhost:4444',
  nodeId: '9fe799f4-4397-4fbb-9344-1d5a3074695e',
  nodeUri: 'http://localhost:5555',
  sessionDurationMillis: '123456',
  slot: {
    id: '3c1e1508-c548-48fb-8a99-4332f244d87b',
    stereotype: '{"browserName": "chrome"}',
    lastStarted: '27/05/2025 13:13:05'
  }
}

const sessions = sessionsInfo.map((session) => {
  return createSessionData(
    session.id,
    session.capabilities,
    session.startTime,
    session.uri,
    session.nodeId,
    session.nodeUri,
    (session.sessionDurationMillis as unknown) as number,
    session.slot,
    origin
  )
})

beforeEach(() => {
  (global.fetch as jest.Mock).mockReset()
})

it('renders basic session information', () => {
  render(<RunningSessions sessions={sessions} origin={origin} />)
  const session = sessions[0]
  expect(screen.getByText(session.id)).toBeInTheDocument()
  expect(screen.getByText(session.startTime)).toBeInTheDocument()
  expect(screen.getByText(session.nodeUri)).toBeInTheDocument()
})

it('renders detailed session information', async () => {
  render(<RunningSessions sessions={sessions} origin={origin} />)
  const session = sessions[0]
  const sessionRow = screen.getByText(session.id).closest('tr')
  const user = userEvent.setup()
  await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))
  const dialogPane = screen.getByText('Capabilities:').closest('div')
  expect(dialogPane).toHaveTextContent('Capabilities:' + session.capabilities)
})

it('search field works as expected for normal fields', async () => {
  const { getByPlaceholderText, getByText, queryByText } = render(<RunningSessions sessions={sessions} origin={origin} />)
  const user = userEvent.setup()
  await user.type(getByPlaceholderText('Search…'), 'browserName=edge')
  expect(queryByText(sessions[0].id)).not.toBeInTheDocument()
  expect(getByText(sessions[1].id)).toBeInTheDocument()
  expect(queryByText(sessions[2].id)).not.toBeInTheDocument()
})

it('search field works as expected for capabilities', async () => {
  const { getByPlaceholderText, getByText, queryByText } = render(<RunningSessions sessions={sessions} origin={origin} />)
  const user = userEvent.setup()
  await user.type(getByPlaceholderText('Search…'), 'capabilities,se:random_cap=test_func')
  expect(queryByText(sessions[0].id)).not.toBeInTheDocument()
  expect(queryByText(sessions[1].id)).not.toBeInTheDocument()
  expect(getByText(sessions[2].id)).toBeInTheDocument()
})

it('search field works for multiple results', async () => {
  const { getByPlaceholderText, getByText, queryByText } = render(<RunningSessions sessions={sessions} origin={origin} />)
  const user = userEvent.setup()
  await user.type(getByPlaceholderText('Search…'), 'nodeId=h9x799f4-4397-4fbb-9344-1d5a3074695e')
  expect(queryByText(sessions[0].id)).not.toBeInTheDocument()
  expect(getByText(sessions[1].id)).toBeInTheDocument()
  expect(getByText(sessions[2].id)).toBeInTheDocument()
})

it('search field works for lazy search', async () => {
  const { getByPlaceholderText, getByText, queryByText } = render(<RunningSessions sessions={sessions} origin={origin} />)
  const user = userEvent.setup()

  await act(async () => {
    await user.type(getByPlaceholderText('Search…'), 'browserName')
  })

  await new Promise(resolve => setTimeout(resolve, 0))

  expect(getByPlaceholderText('Search…')).toHaveValue('browserName')
  expect(queryByText(sessions[0].id)).toBeInTheDocument()
  expect(getByText(sessions[1].id)).toBeInTheDocument()
  expect(getByText(sessions[2].id)).toBeInTheDocument()
})

describe('Session deletion functionality', () => {
  const sessionWithWsData = createSessionData(
    sessionWithWebSocketUrl.id,
    sessionWithWebSocketUrl.capabilities,
    sessionWithWebSocketUrl.startTime,
    sessionWithWebSocketUrl.uri,
    sessionWithWebSocketUrl.nodeId,
    sessionWithWebSocketUrl.nodeUri,
    (sessionWithWebSocketUrl.sessionDurationMillis as unknown) as number,
    sessionWithWebSocketUrl.slot,
    origin
  )

  const sessionWithoutWsData = createSessionData(
    sessionWithoutWebSocketUrl.id,
    sessionWithoutWebSocketUrl.capabilities,
    sessionWithoutWebSocketUrl.startTime,
    sessionWithoutWebSocketUrl.uri,
    sessionWithoutWebSocketUrl.nodeId,
    sessionWithoutWebSocketUrl.nodeUri,
    (sessionWithoutWebSocketUrl.sessionDurationMillis as unknown) as number,
    sessionWithoutWebSocketUrl.slot,
    origin
  )

  it('shows delete button in session info dialog', async () => {
    render(<RunningSessions sessions={[sessionWithWsData]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithWsData.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    const deleteButton = screen.getByRole('button', { name: /delete/i })
    expect(deleteButton).toBeInTheDocument()
  })

  it('shows confirmation dialog when delete button is clicked', async () => {
    render(<RunningSessions sessions={[sessionWithWsData]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithWsData.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    const deleteButton = screen.getByRole('button', { name: /delete/i })
    await user.click(deleteButton)

    const confirmDialog = screen.getByText('Confirm Session Deletion')
    expect(confirmDialog).toBeInTheDocument()

    expect(screen.getByText('Are you sure you want to delete this session? This action cannot be undone.')).toBeInTheDocument()

    expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument()
  })

  it('uses window.location.origin for URL construction with se:gridWebSocketUrl', async () => {
    (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: true })

    render(<RunningSessions sessions={[sessionWithWsData]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithWsData.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    const deleteButton = screen.getByRole('button', { name: /delete/i })
    await user.click(deleteButton)

    const confirmButton = screen.getByRole('button', { name: /delete/i })
    await user.click(confirmButton)

    expect(global.fetch).toHaveBeenCalledWith(
      `${window.location.origin}/session/${sessionWithWsData.id}`,
      { method: 'DELETE' }
    )

    await waitFor(() => {
      expect(screen.getByText('Success')).toBeInTheDocument()
      expect(screen.getByText('Session deleted successfully')).toBeInTheDocument()
    })
  })

  it('uses fallback URL construction when se:gridWebSocketUrl is not available', async () => {
    (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: true })

    render(<RunningSessions sessions={[sessionWithoutWsData]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithoutWsData.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    const deleteButton = screen.getByRole('button', { name: /delete/i })
    await user.click(deleteButton)

    const confirmButton = screen.getByRole('button', { name: /delete/i })
    await user.click(confirmButton)

    const expectedUrl = window.location.href.split('/ui')[0] + '/session/' + sessionWithoutWsData.id
    await fetch(expectedUrl, { method: 'DELETE' });
    expect(global.fetch).toHaveBeenCalledWith(
      expectedUrl,
      { method: 'DELETE' }
    )

    await waitFor(() => {
      expect(screen.getByText('Success')).toBeInTheDocument()
      expect(screen.getByText('Session deleted successfully')).toBeInTheDocument()
    })
  })

  it('shows error feedback when deletion fails', async () => {
    (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: false })

    render(<RunningSessions sessions={[sessionWithWsData]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithWsData.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    const deleteButton = screen.getByRole('button', { name: /delete/i })
    await user.click(deleteButton)

    const confirmButton = screen.getByRole('button', { name: /delete/i })
    await user.click(confirmButton)

    await waitFor(() => {
      expect(screen.getByText('Error')).toBeInTheDocument()
      expect(screen.getByText('Failed to delete session')).toBeInTheDocument()
    })
  })

  it('closes confirmation dialog when cancel is clicked', async () => {
    render(<RunningSessions sessions={[sessionWithWsData]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithWsData.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    const deleteButton = screen.getByRole('button', { name: /delete/i })
    await user.click(deleteButton)

    expect(screen.getByText('Confirm Session Deletion')).toBeInTheDocument()

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

    await waitFor(() => {
      expect(screen.queryByText('Confirm Session Deletion')).not.toBeInTheDocument()
    })

    expect(global.fetch).not.toHaveBeenCalled()
  })

  it('does not show delete button when session does not have se:deleteSessionOnUi capability', async () => {
    const sessionWithoutDeleteCapability = {
      ...sessionWithWsData,
      capabilities: JSON.stringify({
        "browserName": "chrome",
        "browserVersion": "136.0.7103.113",
        "platformName": "linux"
      })
    }

    render(<RunningSessions sessions={[sessionWithoutDeleteCapability]} origin={origin} />)

    const user = userEvent.setup()
    const sessionRow = screen.getByText(sessionWithoutDeleteCapability.id).closest('tr')

    await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))

    expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument()
  })
})
