import { Component, computed, DestroyRef, inject, input, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router";
import { lastValueFrom } from "rxjs";

import {
  AllActivitiesService,
  ApplicationHealthReportDetail,
  ReportStatus,
  RiskInsightsDataService,
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { DialogService, PopoverModule } from "@bitwarden/components";
import { SharedModule } from "@bitwarden/web-vault/app/shared";

import { AccessIntelligenceCoachmarkComponent } from "../onboarding/access-intelligence-coachmark.component";
import { AccessIntelligenceCoachmarkService } from "../onboarding/access-intelligence-coachmark.service";
import { RiskOverTimeService } from "../services/risk-over-time.service";
import { ReportLoadingComponent } from "../shared/report-loading.component";

import { ActivityCardComponent } from "./activity-card.component";
import { PasswordChangeMetricComponent } from "./activity-cards/password-change-metric.component";
import { NewApplicationsDialogComponent } from "./application-review-dialog/new-applications-dialog.component";
import { TimePeriod, DEFAULT_TIME_PERIOD } from "./period-selector/period-selector.types";
import { TrendWidgetComponent, TrendWidgetViewType } from "./trend-widget/trend-widget.component";

// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
  selector: "dirt-all-activity",
  imports: [
    ReportLoadingComponent,
    SharedModule,
    ActivityCardComponent,
    PasswordChangeMetricComponent,
    PopoverModule,
    AccessIntelligenceCoachmarkComponent,
    TrendWidgetComponent,
  ],
  templateUrl: "./all-activity.component.html",
})
export class AllActivityComponent implements OnInit {
  // Prefer component input since route param controls UI state
  readonly organizationId = input.required<OrganizationId>();

  totalCriticalAppsAtRiskMemberCount = 0;
  totalCriticalAppsCount = 0;
  totalCriticalAppsAtRiskCount = 0;
  totalApplicationCount = 0;
  newApplicationsCount = 0;
  newApplications: ApplicationHealthReportDetail[] = [];
  allAppsHaveReviewDate = false;
  isAllCaughtUp = false;
  hasLoadedApplicationData = false;
  showNeedsReviewState = false;

  destroyRef = inject(DestroyRef);

  protected ReportStatusEnum = ReportStatus;

  protected riskOverTimeData$ = this.riskOverTimeService.riskOverTimeData$;
  protected isRiskOverTimeLoading$ = this.riskOverTimeService.isLoading$;
  protected riskOverTimeError$ = this.riskOverTimeService.error$;

  constructor(
    protected activatedRoute: ActivatedRoute,
    protected allActivitiesService: AllActivitiesService,
    protected dataService: RiskInsightsDataService,
    private dialogService: DialogService,
    protected organizationService: OrganizationService,
    protected riskOverTimeService: RiskOverTimeService,
    protected coachmarkService: AccessIntelligenceCoachmarkService,
  ) {}

  async ngOnInit(): Promise<void> {
    this.riskOverTimeService.initialize(
      this.organizationId(),
      DEFAULT_TIME_PERIOD,
      TrendWidgetViewType.Applications,
    );

    this.allActivitiesService.reportSummary$
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((summary) => {
        this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount;
        this.totalCriticalAppsCount = summary.totalCriticalApplicationCount;
        this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount;
        this.totalApplicationCount = summary.totalApplicationCount;
        // If we have application data, mark as loaded
        if (summary.totalApplicationCount > 0) {
          this.hasLoadedApplicationData = true;
        }
        this.updateShowNeedsReviewState();
      });

    this.dataService.newApplications$
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((newApps) => {
        this.newApplications = newApps;
        this.newApplicationsCount = newApps.length;
        this.updateIsAllCaughtUp();
        this.updateShowNeedsReviewState();
      });

    this.dataService.enrichedReportData$
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((enrichedData) => {
        if (enrichedData?.applicationData && enrichedData.applicationData.length > 0) {
          this.hasLoadedApplicationData = true;
          // Check if all apps have a review date (not null and not undefined)
          this.allAppsHaveReviewDate = enrichedData.applicationData.every(
            (app) => app.reviewedDate !== null && app.reviewedDate !== undefined,
          );
        } else {
          this.hasLoadedApplicationData = enrichedData !== null;
          this.allAppsHaveReviewDate = false;
        }
        this.updateIsAllCaughtUp();
      });
  }

  /**
   * Updates the isAllCaughtUp flag based on current state.
   * Only shows "All caught up!" when:
   * - Data has been loaded (hasLoadedApplicationData is true)
   * - No new applications need review
   * - All apps have a review date
   */
  private updateIsAllCaughtUp(): void {
    this.isAllCaughtUp =
      this.hasLoadedApplicationData &&
      this.newApplicationsCount === 0 &&
      this.allAppsHaveReviewDate;
  }

  /**
   * Updates the showNeedsReviewState flag based on current state.
   * This state is shown when:
   * - Data has been loaded
   * - There are applications (totalApplicationCount > 0)
   * - ALL apps do NOT have a review date (newApplicationsCount === totalApplicationCount)
   */
  private updateShowNeedsReviewState(): void {
    this.showNeedsReviewState =
      this.hasLoadedApplicationData &&
      this.totalApplicationCount > 0 &&
      this.newApplicationsCount === this.totalApplicationCount;
  }

  onTimespanChanged(timeframe: TimePeriod): void {
    this.riskOverTimeService.setTimeframe(timeframe);
  }

  onViewChanged(dataView: TrendWidgetViewType): void {
    this.riskOverTimeService.setDataView(dataView);
  }

  /**
   * Handles the review new applications button click.
   * Opens a dialog showing the list of new applications that can be marked as critical.
   */
  async onReviewNewApplications() {
    const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId");

    if (!organizationId) {
      return;
    }

    // Pass organizationId via dialog data instead of having the dialog retrieve it from route.
    // This ensures organizationId is immediately available when dialog opens, preventing
    // timing issues where the dialog's checkForTasksToAssign() method runs before
    // organizationId is populated via async route subscription.
    const dialogRef = NewApplicationsDialogComponent.open(this.dialogService, {
      newApplications: this.newApplications,
      organizationId: organizationId as OrganizationId,
      hasExistingCriticalApplications: this.totalCriticalAppsCount > 0,
    });

    await lastValueFrom(dialogRef.closed);
  }

  /**
   * Handles the "View at-risk members" link click.
   * Opens the at-risk members drawer for critical applications only.
   */
  async onViewAtRiskMembers() {
    await this.dataService.setDrawerForCriticalAtRiskMembers("activityTabAtRiskMembers");
  }

  /**
   * Handles the "View at-risk applications" link click.
   * Opens the at-risk applications drawer for critical applications only.
   */
  async onViewAtRiskApplications() {
    await this.dataService.setDrawerForCriticalAtRiskApps("activityTabAtRiskApplications");
  }

  protected readonly prioritizeRisksOpen = computed(
    () => this.coachmarkService.activeStepId() === "prioritizeRisks",
  );
}
