import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';

import _ from 'lodash';
import { ToastrModule } from 'ngx-toastr';
import { BehaviorSubject, of } from 'rxjs';

import { HealthService } from '~/app/shared/api/health.service';
import { PrometheusService } from '~/app/shared/api/prometheus.service';
import { CssHelper } from '~/app/shared/classes/css-helper';
import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts';
import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service';
import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
import { SummaryService } from '~/app/shared/services/summary.service';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
import { PgCategoryService } from '../../shared/pg-category.service';
import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component';
import { PgSummaryPipe } from '../pg-summary.pipe';
import { DashboardV3Component } from './dashboard-v3.component';
import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
import { AlertClass } from '~/app/shared/enum/health-icon.enum';
import { HealthSnapshotMap } from '~/app/shared/models/health.interface';
import { VERSION_PREFIX } from '~/app/shared/constants/app.constants';

export class SummaryServiceMock {
  summaryDataSource = new BehaviorSubject({
    version:
      `${VERSION_PREFIX} 17.0.0-12222-gcd0cd7cb ` +
      '(b8193bb4cda16ccc5b028c3e1df62bc72350a15d) quincy (dev)'
  });
  summaryData$ = this.summaryDataSource.asObservable();

  subscribe(call: any) {
    return this.summaryData$.subscribe(call);
  }
}

describe('Dashbord Component', () => {
  let component: DashboardV3Component;
  let fixture: ComponentFixture<DashboardV3Component>;
  let orchestratorService: OrchestratorService;
  let getHealthStatusSpy: jasmine.Spy;
  let getAlertsSpy: jasmine.Spy;
  let fakeFeatureTogglesService: jasmine.Spy;

  const healthStatusPayload: HealthSnapshotMap = {
    fsid: '7d0cc9da-ca8d-4539-a953-ab062139c26a',
    health: {
      status: 'HEALTH_WARN',
      checks: {
        DASHBOARD_DEBUG: {
          severity: 'HEALTH_WARN',
          summary: {
            message: 'Dashboard debug mode is enabled',
            count: 0
          },
          muted: false
        }
      },
      mutes: []
    },
    monmap: {
      num_mons: 3
    },
    osdmap: {
      in: 3,
      up: 3,
      num_osds: 3
    },
    pgmap: {
      pgs_by_state: [
        {
          state_name: 'active+clean',
          count: 497
        }
      ],
      num_pools: 14,
      bytes_used: 3236978688,
      bytes_total: 325343772672,
      num_pgs: 497
    },
    mgrmap: {
      num_active: 1,
      num_standbys: 0
    },
    fsmap: {
      num_standbys: 2,
      num_active: 1
    },
    num_rgw_gateways: 3,
    num_iscsi_gateways: {
      up: 0,
      down: 0
    },
    num_hosts: 1
  };

  const alertsPayload: AlertmanagerAlert[] = [
    {
      labels: {
        alertname: 'CephMgrPrometheusModuleInactive',
        instance: 'ceph2:9283',
        job: 'ceph',
        severity: 'critical'
      },
      annotations: {
        description: 'The mgr/prometheus module at ceph2:9283 is unreachable.',
        summary: 'The mgr/prometheus module is not available'
      },
      startsAt: '2022-09-28T08:23:41.152Z',
      endsAt: '2022-09-28T15:28:01.152Z',
      generatorURL: 'http://prometheus:9090/testUrl',
      status: {
        state: 'active',
        silencedBy: null,
        inhibitedBy: null
      },
      receivers: ['ceph2'],
      fingerprint: 'fingerprint',
      alert_count: 1
    },
    {
      labels: {
        alertname: 'CephOSDDownHigh',
        instance: 'ceph:9283',
        job: 'ceph',
        severity: 'critical'
      },
      annotations: {
        description: '66.67% or 2 of 3 OSDs are down (>= 10%).',
        summary: 'More than 10% of OSDs are down'
      },
      startsAt: '2022-09-28T14:17:22.665Z',
      endsAt: '2022-09-28T15:28:32.665Z',
      generatorURL: 'http://prometheus:9090/testUrl',
      status: {
        state: 'active',
        silencedBy: null,
        inhibitedBy: null
      },
      receivers: ['default'],
      fingerprint: 'fingerprint',
      alert_count: 1
    },
    {
      labels: {
        alertname: 'CephHealthWarning',
        instance: 'ceph:9283',
        job: 'ceph',
        severity: 'warning'
      },
      annotations: {
        description: 'The cluster state has been HEALTH_WARN for more than 15 minutes.',
        summary: 'Ceph is in the WARNING state'
      },
      startsAt: '2022-09-28T08:41:38.454Z',
      endsAt: '2022-09-28T15:28:38.454Z',
      generatorURL: 'http://prometheus:9090/testUrl',
      status: {
        state: 'active',
        silencedBy: null,
        inhibitedBy: null
      },
      receivers: ['ceph'],
      fingerprint: 'fingerprint',
      alert_count: 1
    }
  ];

  const orchName: any = 'Cephadm';

  configureTestBed({
    imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule],
    declarations: [DashboardV3Component, DashboardPieComponent, PgSummaryPipe],
    schemas: [NO_ERRORS_SCHEMA],
    providers: [
      { provide: SummaryService, useClass: SummaryServiceMock },
      PrometheusAlertService,
      CssHelper,
      PgCategoryService
    ]
  });

  beforeEach(() => {
    fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue(
      of({
        rbd: true,
        mirroring: true,
        iscsi: true,
        cephfs: true,
        rgw: true
      })
    );
    fixture = TestBed.createComponent(DashboardV3Component);
    component = fixture.componentInstance;
    orchestratorService = TestBed.inject(OrchestratorService);
    getHealthStatusSpy = spyOn(TestBed.inject(HealthService), 'getHealthSnapshot');
    getHealthStatusSpy.and.returnValue(of(healthStatusPayload));
    getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts');
    getAlertsSpy.and.returnValue(of(alertsPayload));
    component.prometheusAlertService.alerts = alertsPayload;
    component.isAlertmanagerConfigured = true;
    let prometheusAlertService = TestBed.inject(PrometheusAlertService);
    spyOn(prometheusAlertService, 'getGroupedAlerts').and.callFake(() => of([]));
    prometheusAlertService.activeCriticalAlerts = 2;
    prometheusAlertService.activeWarningAlerts = 1;
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render all cards', () => {
    fixture.detectChanges();
    const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card');
    expect(dashboardCards.length).toBe(5);
  });

  it('should get corresponding data into detailsCardData', () => {
    spyOn(orchestratorService, 'getName').and.returnValue(of(orchName));
    component.ngOnInit();
    expect(component.detailsCardData.fsid).toBe(healthStatusPayload['fsid']);
    expect(component.detailsCardData.orchestrator).toBe('Cephadm');
    expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)');
  });

  it('should check if the respective icon is shown for each status', () => {
    const payload = _.cloneDeep(healthStatusPayload);

    // HEALTH_WARN
    payload.health['status'] = 'HEALTH_WARN';
    payload.health['checks'] = {
      FAKE_CHECK: {
        severity: 'HEALTH_WARN',
        summary: { message: 'fake warning', count: 1 },
        muted: false
      }
    };

    getHealthStatusSpy.and.returnValue(of(payload));
    fixture.detectChanges();
    const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i'));
    expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);

    // HEALTH_ERR
    payload.health['status'] = 'HEALTH_ERR';
    payload.health['checks'] = {
      FAKE_CHECK: {
        severity: 'HEALTH_ERR',
        summary: { message: 'fake error', count: 1 },
        muted: false
      }
    };

    getHealthStatusSpy.and.returnValue(of(payload));
    fixture.detectChanges();
    expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);

    // HEALTH_OK
    payload.health['status'] = 'HEALTH_OK';
    payload.health['checks'] = {};

    getHealthStatusSpy.and.returnValue(of(payload));
    fixture.detectChanges();
    expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
  });

  it('should show the actual alert count on each alerts pill', () => {
    fixture.detectChanges();

    const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts] span'));

    const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts] span'));

    expect(warningAlerts.nativeElement.textContent).toBe('1');
    expect(dangerAlerts.nativeElement.textContent).toBe('2');
  });

  it('should show the critical alerts window and its content', () => {
    const payload = _.cloneDeep(alertsPayload[0]);
    component.toggleAlertsWindow(AlertClass[0]);
    fixture.detectChanges();

    const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));

    expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
    expect(component.alertType).not.toBe('warning');
  });

  it('should show the warning alerts window and its content', () => {
    const payload = _.cloneDeep(alertsPayload[2]);
    component.toggleAlertsWindow(AlertClass.warning);
    fixture.detectChanges();

    const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));

    expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
    expect(component.alertType).not.toBe('critical');
  });

  it('should only show the pills when the alerts are not empty', () => {
    spyOn(TestBed.inject(PrometheusAlertService), 'alerts').and.returnValue(0);
    fixture.detectChanges();

    const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts]'));

    const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts]'));

    expect(warningAlerts).toBe(null);
    expect(dangerAlerts).toBe(null);
  });

  it('should render "Status" card text that is not clickable', () => {
    const payload = _.cloneDeep(healthStatusPayload);
    payload.health['status'] = 'HEALTH_OK';
    payload.health['checks'] = null;

    getHealthStatusSpy.and.returnValue(of(payload));
    fixture.detectChanges();
    const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
    const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
    expect(clickableContent).toBeNull();
  });

  it('should render "Status" card text that is clickable (popover)', () => {
    const payload = _.cloneDeep(healthStatusPayload);
    payload.health['status'] = 'HEALTH_WARN';
    payload.health['checks'] = {
      FAKE_CHECK: {
        severity: 'HEALTH_WARN',
        summary: { message: 'fake warning', count: 1 },
        muted: false
      }
    };

    getHealthStatusSpy.and.returnValue(of(payload));
    fixture.detectChanges();

    const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
    const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
    expect(clickableContent).not.toBeNull();
  });

  describe('features disabled', () => {
    beforeEach(() => {
      fakeFeatureTogglesService.and.returnValue(
        of({
          rbd: false,
          mirroring: false,
          iscsi: false,
          cephfs: false,
          rgw: false
        })
      );
      fixture = TestBed.createComponent(DashboardV3Component);
      component = fixture.componentInstance;
    });

    it('should not render items related to disabled features', () => {
      fixture.detectChanges();

      const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]'));
      const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]'));
      const mds = fixture.debugElement.query(By.css('li[id=mds-item]'));

      expect(iscsiCard).toBeFalsy();
      expect(rgwCard).toBeFalsy();
      expect(mds).toBeFalsy();
    });
  });
});
