import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import {
  HttpTestingController,
  provideHttpClientTesting,
} from '@angular/common/http/testing'
import { fakeAsync, TestBed, tick } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterTestingModule } from '@angular/router/testing'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { CookieService } from 'ngx-cookie-service'
import { of, Subscription } from 'rxjs'
import { environment } from 'src/environments/environment'
import { CustomFieldDataType } from '../data/custom-field'
import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
import { SavedView } from '../data/saved-view'
import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings'
import { PermissionsService } from './permissions.service'
import { CustomFieldsService } from './rest/custom-fields.service'
import { SettingsService } from './settings.service'
import { ToastService } from './toast.service'

const customFields = [
  {
    id: 1,
    name: 'Field 1',
    created: new Date(),
    data_type: CustomFieldDataType.Monetary,
  },
  {
    id: 2,
    name: 'Field 2',
    created: new Date(),
    data_type: CustomFieldDataType.String,
  },
]

describe('SettingsService', () => {
  let httpTestingController: HttpTestingController
  let settingsService: SettingsService
  let cookieService: CookieService
  let customFieldsService: CustomFieldsService
  let permissionService: PermissionsService
  let subscription: Subscription
  let toastService: ToastService

  const ui_settings: UiSettings = {
    user: {
      username: 'testuser',
      first_name: 'Test',
      last_name: 'User',
      id: 1,
      is_superuser: true,
    },
    settings: {
      language: '',
      bulk_edit: { confirmation_dialogs: true, apply_on_close: false },
      documentListSize: 50,
      dark_mode: { use_system: true, enabled: 'false', thumb_inverted: 'true' },
      theme: { color: '#9fbf2f' },
      document_details: { native_pdf_viewer: false },
      date_display: { date_locale: '', date_format: 'mediumDate' },
      notifications: {
        consumer_new_documents: true,
        consumer_success: true,
        consumer_failed: true,
        consumer_suppress_on_dashboard: true,
      },
      comments_enabled: true,
      slim_sidebar: false,
      update_checking: { enabled: false, backend_setting: 'default' },
      saved_views: { warn_on_unsaved_change: true },
      notes_enabled: true,
      auditlog_enabled: true,
      tour_complete: false,
      permissions: {
        default_owner: null,
        default_view_users: [1],
        default_view_groups: [2],
        default_edit_users: [3],
        default_edit_groups: [4],
      },
    },
    permissions: [],
  }

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [],
      imports: [
        RouterTestingModule,
        NgbModule,
        FormsModule,
        ReactiveFormsModule,
      ],
      providers: [
        SettingsService,
        CookieService,
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
      ],
    })

    httpTestingController = TestBed.inject(HttpTestingController)
    cookieService = TestBed.inject(CookieService)
    customFieldsService = TestBed.inject(CustomFieldsService)
    permissionService = TestBed.inject(PermissionsService)
    settingsService = TestBed.inject(SettingsService)
    toastService = TestBed.inject(ToastService)
    // Normally done in app initializer
    settingsService.initializeSettings().subscribe()
  })

  afterEach(() => {
    subscription?.unsubscribe()
    // httpTestingController.verify()
  })

  it('calls ui_settings api endpoint on initialize', () => {
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    expect(req.request.method).toEqual('GET')
  })

  it('should catch error and show toast on retrieve ui_settings error', fakeAsync(() => {
    const toastSpy = jest.spyOn(toastService, 'showError')
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(
        { detail: 'You do not have permission to perform this action.' },
        { status: 403, statusText: 'Forbidden' }
      )
    tick(500)
    expect(toastSpy).toHaveBeenCalled()
  }))

  it('calls ui_settings api endpoint with POST on store', () => {
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(ui_settings)

    subscription = settingsService.storeSettings().subscribe()
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    expect(req.request.method).toEqual('POST')
    expect(req.request.body).toEqual({
      settings: ui_settings.settings,
    })
  })

  it('correctly loads settings of various types', () => {
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(ui_settings)

    expect(settingsService.displayName).toEqual('Test')
    expect(settingsService.getLanguage()).toEqual('')
    expect(settingsService.get(SETTINGS_KEYS.DARK_MODE_ENABLED)).toBeFalsy()
    expect(
      settingsService.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)
    ).toBeTruthy()
    expect(settingsService.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)).toEqual(50)
    expect(settingsService.get(SETTINGS_KEYS.THEME_COLOR)).toEqual('#9fbf2f')
  })

  it('correctly allows updating settings of various types', () => {
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(ui_settings)

    settingsService.setLanguage('de-de')
    settingsService.set(SETTINGS_KEYS.DARK_MODE_ENABLED, true)
    settingsService.set(
      SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT,
      false
    )
    settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 25)
    settingsService.set(SETTINGS_KEYS.THEME_COLOR, '#000000')

    expect(settingsService.getLanguage()).toEqual('de-de')
    expect(settingsService.get(SETTINGS_KEYS.DARK_MODE_ENABLED)).toBeTruthy()
    expect(
      settingsService.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)
    ).toBeFalsy()
    expect(settingsService.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)).toEqual(25)
    expect(settingsService.get(SETTINGS_KEYS.THEME_COLOR)).toEqual('#000000')
  })

  it('sets django cookie for languages', () => {
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(ui_settings)
    const cookieSetSpy = jest.spyOn(cookieService, 'set')
    settingsService.initializeSettings().subscribe(() => {})
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    ui_settings.settings['language'] = 'foobar'
    req.flush(ui_settings)
    expect(cookieSetSpy).toHaveBeenCalledWith('django_language', 'foobar')
    const cookieDeleteSpy = jest.spyOn(cookieService, 'delete')
    settingsService.setLanguage('')
    expect(cookieDeleteSpy).toHaveBeenCalled()
  })

  it('should support null values for settings if set, undefined if not', () => {
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(ui_settings)
    expect(settingsService.get('foo')).toEqual(undefined)
    expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER)).toEqual(null)
  })

  it('should support array values', () => {
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(ui_settings)
    expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS)).toEqual(
      [1]
    )
  })

  it('should support default permissions values', () => {
    delete ui_settings.settings['permissions']
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(ui_settings)
    expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER)).toEqual(1)
    expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS)).toEqual(
      []
    )
  })

  it('updates appearance settings', () => {
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(ui_settings)

    expect(
      document.documentElement.style.getPropertyValue(
        '--pngx-primary-lightness'
      )
    ).toEqual('')

    const addClassSpy = jest.spyOn(settingsService.renderer, 'addClass')
    const setAttributeSpy = jest.spyOn(settingsService.renderer, 'setAttribute')

    settingsService.updateAppearanceSettings(true, true, '#fff000')
    expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-light')
    expect(setAttributeSpy).toHaveBeenCalledWith(
      document.documentElement,
      'data-bs-theme',
      'auto'
    )
    expect(
      document.documentElement.style.getPropertyValue(
        '--pngx-primary-lightness'
      )
    ).toEqual('50%')

    settingsService.updateAppearanceSettings(false, false, '#000000')
    expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-light')
    expect(setAttributeSpy).toHaveBeenCalledWith(
      document.documentElement,
      'data-bs-theme',
      'light'
    )

    expect(
      document.documentElement.style.getPropertyValue(
        '--pngx-primary-lightness'
      )
    ).toEqual('0%')

    settingsService.updateAppearanceSettings(false, true, '#ffffff')
    expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-dark')
    expect(setAttributeSpy).toHaveBeenCalledWith(
      document.documentElement,
      'data-bs-theme',
      'dark'
    )
    expect(
      document.documentElement.style.getPropertyValue(
        '--pngx-primary-lightness'
      )
    ).toEqual('100%')
  })

  it('migrates settings automatically', () => {
    const oldSettings = Object.assign({}, ui_settings)
    delete oldSettings.settings['documentListSize']
    window.localStorage.setItem(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, '50')
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(oldSettings)

    req = httpTestingController.match(
      `${environment.apiBaseUrl}ui_settings/`
    )[0]
    expect(req.request.method).toEqual('POST')
  })

  it('updates settings on complete tour', () => {
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(ui_settings)

    settingsService.completeTour()

    req = httpTestingController.match(
      `${environment.apiBaseUrl}ui_settings/`
    )[0]
    expect(req.request.method).toEqual('POST')
  })

  it('should update saved view sorting', () => {
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(ui_settings)
    const setSpy = jest.spyOn(settingsService, 'set')
    settingsService.updateDashboardViewsSort([
      { id: 1 } as SavedView,
      { id: 4 } as SavedView,
    ])
    expect(setSpy).toHaveBeenCalledWith(
      SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER,
      [1, 4]
    )
    settingsService.updateSidebarViewsSort([
      { id: 1 } as SavedView,
      { id: 4 } as SavedView,
    ])
    expect(setSpy).toHaveBeenCalledWith(
      SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER,
      [1, 4]
    )
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
      .flush(ui_settings)
  })

  it('should update environment app title if set', () => {
    const settings = Object.assign({}, ui_settings)
    settings.settings['app_title'] = 'FooBar'
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(settings)
    expect(environment.appTitle).toEqual('FooBar')
    // post for migrate
    httpTestingController.expectOne(`${environment.apiBaseUrl}ui_settings/`)
  })

  it('should hide fields if no perms or disabled', () => {
    jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}ui_settings/`
    )
    req.flush(ui_settings)
    settingsService.initializeDisplayFields()
    expect(
      settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[0])
    ).toBeTruthy() // title
    expect(
      settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[4])
    ).toBeFalsy() // correspondent

    settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
    settingsService.initializeDisplayFields()
    expect(
      settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[8])
    ).toBeFalsy() // notes

    jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
    settingsService.initializeDisplayFields()
    expect(
      settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[4])
    ).toBeTruthy() // correspondent
  })

  it('should dynamically create display fields options including custom fields', () => {
    jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
    jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
      of({
        all: customFields.map((f) => f.id),
        count: customFields.length,
        results: customFields.concat([]),
      })
    )
    settingsService.initializeDisplayFields()
    expect(
      settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[0])
    ).toBeTruthy()
    expect(
      settingsService.allDisplayFields.find(
        (f) => f.id === `${DisplayField.CUSTOM_FIELD}${customFields[0].id}`
      ).name
    ).toEqual(customFields[0].name)
  })
})
