import {
  HttpEventType,
  HttpResponse,
  provideHttpClient,
  withInterceptorsFromDi,
} from '@angular/common/http'
import {
  HttpTestingController,
  provideHttpClientTesting,
} from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import WS from 'jest-websocket-mock'
import { environment } from 'src/environments/environment'
import { DocumentService } from './rest/document.service'
import { SettingsService } from './settings.service'
import {
  FILE_STATUS_MESSAGES,
  FileStatusPhase,
  WebsocketStatusService,
  WebsocketStatusType,
} from './websocket-status.service'

describe('ConsumerStatusService', () => {
  let httpTestingController: HttpTestingController
  let websocketStatusService: WebsocketStatusService
  let documentService: DocumentService
  let settingsService: SettingsService

  const server = new WS(
    `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`,
    { jsonProtocol: true }
  )

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        WebsocketStatusService,
        DocumentService,
        SettingsService,
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
      ],
    })

    httpTestingController = TestBed.inject(HttpTestingController)
    settingsService = TestBed.inject(SettingsService)
    settingsService.currentUser = {
      id: 1,
      username: 'testuser',
      is_superuser: false,
    }
    websocketStatusService = TestBed.inject(WebsocketStatusService)
    documentService = TestBed.inject(DocumentService)
  })

  afterEach(() => {
    httpTestingController.verify()
  })

  it('should update status on websocket processing progress', () => {
    const task_id = '1234'
    const status = websocketStatusService.newFileUpload('file.pdf')
    expect(status.getProgress()).toEqual(0)

    websocketStatusService.connect()

    websocketStatusService
      .onDocumentConsumptionFinished()
      .subscribe((filestatus) => {
        expect(filestatus.phase).toEqual(FileStatusPhase.SUCCESS)
      })

    websocketStatusService.onDocumentDetected().subscribe((filestatus) => {
      expect(filestatus.phase).toEqual(FileStatusPhase.STARTED)
    })

    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id,
        filename: 'file.pdf',
        current_progress: 50,
        max_progress: 100,
        document_id: 12,
        status: 'WORKING',
      },
    })

    expect(status.getProgress()).toBeCloseTo(0.6) // (0.8 * 50/100) + .2
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
      status,
    ])

    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id,
        filename: 'file.pdf',
        current_progress: 100,
        max_progress: 100,
        document_id: 12,
        status: 'SUCCESS',
        message: FILE_STATUS_MESSAGES.finished,
      },
    })

    expect(status.getProgress()).toEqual(1)
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      0
    )
    expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)

    websocketStatusService.disconnect()
  })

  it('should update status on websocket failed progress', () => {
    const task_id = '1234'
    const status = websocketStatusService.newFileUpload('file.pdf')
    status.taskId = task_id
    websocketStatusService.connect()

    websocketStatusService
      .onDocumentConsumptionFailed()
      .subscribe((filestatus) => {
        expect(filestatus.phase).toEqual(FileStatusPhase.FAILED)
      })

    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id,
        filename: 'file.pdf',
        current_progress: 50,
        max_progress: 100,
        document_id: 12,
      },
    })

    expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
      status,
    ])

    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id,
        filename: 'file.pdf',
        current_progress: 50,
        max_progress: 100,
        document_id: 12,
        status: 'FAILED',
        message: FILE_STATUS_MESSAGES.document_already_exists,
      },
    })

    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      0
    )
    expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
  })

  it('should update status on upload progress', () => {
    const task_id = '1234'
    const status = websocketStatusService.newFileUpload('file.pdf')

    documentService.uploadDocument({}).subscribe((event) => {
      if (event.type === HttpEventType.Response) {
        status.taskId = event.body['task_id']
        status.message = $localize`Upload complete, waiting...`
      } else if (event.type === HttpEventType.UploadProgress) {
        status.updateProgress(
          FileStatusPhase.UPLOADING,
          event.loaded,
          event.total
        )
      }
    })

    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/post_document/`
    )

    req.event(
      new HttpResponse({
        body: {
          task_id,
        },
      })
    )

    req.event({
      type: HttpEventType.UploadProgress,
      loaded: 100,
      total: 300,
    })

    expect(
      websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
    ).toEqual([status])
    expect(websocketStatusService.getConsumerStatus()).toEqual([status])
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
      status,
    ])

    req.event({
      type: HttpEventType.UploadProgress,
      loaded: 300,
      total: 300,
    })

    expect(status.getProgress()).toEqual(0.2) // 0.2 * 300/300
  })

  it('should support dismiss completed', () => {
    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '1234',
        filename: 'file.pdf',
        current_progress: 100,
        max_progress: 100,
        document_id: 12,
        status: 'SUCCESS',
        message: 'finished',
      },
    })

    expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
    websocketStatusService.dismissCompleted()
    expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(0)
    websocketStatusService.disconnect()
  })

  it('should support dismiss', () => {
    const task_id = '1234'
    const status = websocketStatusService.newFileUpload('file.pdf')
    status.taskId = task_id
    status.updateProgress(FileStatusPhase.UPLOADING, 50, 100)

    const status2 = websocketStatusService.newFileUpload('file2.pdf')
    status2.updateProgress(FileStatusPhase.UPLOADING, 50, 100)

    expect(
      websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
    ).toEqual([status, status2])
    expect(websocketStatusService.getConsumerStatus()).toEqual([
      status,
      status2,
    ])
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
      status,
      status2,
    ])

    websocketStatusService.dismiss(status)
    expect(websocketStatusService.getConsumerStatus()).toEqual([status2])

    websocketStatusService.dismiss(status2)
    expect(websocketStatusService.getConsumerStatus()).toHaveLength(0)
  })

  it('should support fail', () => {
    const task_id = '1234'
    const status = websocketStatusService.newFileUpload('file.pdf')
    status.taskId = task_id
    status.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      1
    )
    expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(0)
    websocketStatusService.fail(status, 'fail')
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      0
    )
    expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
  })

  it('should notify of document created on status message without upload', () => {
    let detected = false
    websocketStatusService.onDocumentDetected().subscribe((filestatus) => {
      expect(filestatus.phase).toEqual(FileStatusPhase.STARTED)
      detected = true
    })

    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '1234',
        filename: 'file.pdf',
        current_progress: 0,
        max_progress: 100,
        message: 'new_file',
        status: 'STARTED',
      },
    })

    websocketStatusService.disconnect()
    expect(detected).toBeTruthy()
  })

  it('should notify of document in progress without upload', () => {
    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '1234',
        filename: 'file.pdf',
        current_progress: 50,
        max_progress: 100,
        docuement_id: 12,
        status: 'WORKING',
      },
    })

    websocketStatusService.disconnect()
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      1
    )
  })

  it('should not notify current user if document has different expected owner', () => {
    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '1234',
        filename: 'file1.pdf',
        current_progress: 50,
        max_progress: 100,
        docuement_id: 12,
        owner_id: 1,
        status: 'WORKING',
      },
    })

    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '5678',
        filename: 'file2.pdf',
        current_progress: 50,
        max_progress: 100,
        docuement_id: 13,
        owner_id: 2,
        status: 'WORKING',
      },
    })

    websocketStatusService.disconnect()
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      1
    )
  })

  it('should notify user if user can view or is in group', () => {
    settingsService.currentUser = {
      id: 1,
      username: 'testuser',
      is_superuser: false,
      groups: [1],
    }
    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '1234',
        filename: 'file1.pdf',
        current_progress: 50,
        max_progress: 100,
        docuement_id: 12,
        owner_id: 2,
        status: 'WORKING',
        users_can_view: [1],
        groups_can_view: [],
      },
    })
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      1
    )
    server.send({
      type: WebsocketStatusType.STATUS_UPDATE,
      data: {
        task_id: '5678',
        filename: 'file2.pdf',
        current_progress: 50,
        max_progress: 100,
        docuement_id: 13,
        owner_id: 2,
        status: 'WORKING',
        users_can_view: [],
        groups_can_view: [1],
      },
    })
    expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
      2
    )
  })

  it('should trigger deleted subject on document deleted', () => {
    let deleted = false
    websocketStatusService.onDocumentDeleted().subscribe(() => {
      deleted = true
    })

    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.DOCUMENTS_DELETED,
      data: {
        documents: [1, 2, 3],
      },
    })

    websocketStatusService.disconnect()
    expect(deleted).toBeTruthy()
  })

  it('should trigger updated subject on document updated', () => {
    let updated = false
    websocketStatusService.onDocumentUpdated().subscribe((data) => {
      updated = true
      expect(data.document_id).toEqual(12)
    })

    websocketStatusService.connect()
    server.send({
      type: WebsocketStatusType.DOCUMENT_UPDATED,
      data: {
        document_id: 12,
        modified: '2026-02-17T00:00:00Z',
        owner_id: 1,
      },
    })

    websocketStatusService.disconnect()
    expect(updated).toBeTruthy()
  })

  it('should ignore document updated events the user cannot view', () => {
    let updated = false
    websocketStatusService.onDocumentUpdated().subscribe(() => {
      updated = true
    })

    websocketStatusService.handleDocumentUpdated({
      document_id: 12,
      modified: '2026-02-17T00:00:00Z',
      owner_id: 2,
      users_can_view: [],
      groups_can_view: [],
    })

    expect(updated).toBeFalsy()
  })
})
