import { Injectable, inject } from '@angular/core';
import { createEffect, ofType } from '@ngrx/effects';
import { setCurrentTask } from '../../../tasks/store/task.actions';
import { TaskSharedActions } from '../../../../root-store/meta/task-shared.actions';
import {
  concatMap,
  filter,
  map,
  switchMap,
  take,
  tap,
  withLatestFrom,
} from 'rxjs/operators';
import { select, Store } from '@ngrx/store';
import { OPEN_PROJECT_TYPE } from '../../issue.const';
import { MatDialog } from '@angular/material/dialog';
import { Task, TaskCopy } from '../../../tasks/task.model';
import { OpenProjectCfg, OpenProjectTransitionOption } from './open-project.model';
import { EMPTY, from, Observable, of, timer } from 'rxjs';
import { OpenProjectApiService } from './open-project-api.service';
import { TaskService } from '../../../tasks/task.service';
import { selectCurrentTaskParentOrCurrent } from 'src/app/features/tasks/store/task.selectors';
import { isOpenProjectEnabled } from './is-open-project-enabled.util';
import { IssueLocalState } from 'src/app/features/issue/issue.model';
import { SnackService } from 'src/app/core/snack/snack.service';
import { OpenProjectWorkPackage } from './open-project-issue.model';
import { IssueService } from 'src/app/features/issue/issue.service';
import { T } from 'src/app/t.const';
import { IssueProviderService } from '../../issue-provider.service';
import { assertTruthy } from '../../../../util/assert-truthy';
import { LOCAL_ACTIONS } from '../../../../util/local-actions.token';
import { parseOpenProjectDuration } from './open-project-view-components/parse-open-project-duration.util';
import { formatOpenProjectWorkPackageSubjectForSnack } from './format-open-project-work-package-subject.util';
import { msToIsoDuration } from '../../../../util/ms-to-iso-duration';
import { getDbDateStr } from 'src/app/util/get-db-date-str';
import { TrackTimeSubmitParams } from '../../shared/dialog-track-time/track-time-dialog.model';

@Injectable()
export class OpenProjectEffects {
  private readonly _actions$ = inject(LOCAL_ACTIONS);
  private readonly _store$ = inject(Store);
  private readonly _snackService = inject(SnackService);
  private readonly _openProjectApiService = inject(OpenProjectApiService);
  private readonly _issueProviderService = inject(IssueProviderService);
  private readonly _matDialog = inject(MatDialog);
  private readonly _taskService = inject(TaskService);
  private readonly _issueService = inject(IssueService);

  postTime$ = createEffect(
    () =>
      this._actions$.pipe(
        ofType(TaskSharedActions.updateTask),
        filter(({ task }) => task.changes.isDone === true),
        concatMap(({ task }) => this._taskService.getByIdOnce$(task.id as string)),
        filter((task) => !!task),
        concatMap((task) =>
          task.parentId
            ? this._taskService
                .getByIdOnce$(task.parentId)
                .pipe(map((parent) => ({ mainTask: parent, subTask: task })))
            : of({ mainTask: task, subTask: undefined }),
        ),
        filter(({ mainTask }) => !!mainTask),
        concatMap(({ mainTask, subTask }) =>
          mainTask.issueType === OPEN_PROJECT_TYPE &&
          mainTask.issueId &&
          mainTask.issueProviderId
            ? this._getCfgOnce$(mainTask.issueProviderId).pipe(
                tap((openProjectCfg) => {
                  if (
                    subTask &&
                    openProjectCfg.isShowTimeTrackingDialogForEachSubTask &&
                    openProjectCfg.isShowTimeTrackingDialog
                  ) {
                    this._openTrackTimeDialog(
                      subTask,
                      // TODO looks fishy???
                      +assertTruthy(mainTask.issueId),
                      openProjectCfg,
                    );
                  } else if (
                    openProjectCfg.isShowTimeTrackingDialog &&
                    !subTask &&
                    (!openProjectCfg.isShowTimeTrackingDialogForEachSubTask ||
                      !mainTask.subTaskIds.length)
                  ) {
                    this._openTrackTimeDialog(
                      mainTask,
                      // TODO looks fishy???
                      +assertTruthy(mainTask.issueId),
                      openProjectCfg,
                    );
                  }
                }),
              )
            : EMPTY,
        ),
      ),
    { dispatch: false },
  );

  checkForStartTransition$ = createEffect(
    () =>
      this._actions$.pipe(
        ofType(setCurrentTask),
        // only if a task is started
        filter(({ id }) => !!id),
        withLatestFrom(this._store$.pipe(select(selectCurrentTaskParentOrCurrent))),
        filter(
          (input): input is [(typeof input)[0], Readonly<TaskCopy>] =>
            input[1] != null && input[1].issueType === OPEN_PROJECT_TYPE,
        ),
        concatMap(([, currentTaskOrParent]) => {
          if (!currentTaskOrParent.issueProviderId) {
            throw new Error('No issueProviderId for task');
          }
          return this._getCfgOnce$(currentTaskOrParent.issueProviderId).pipe(
            map((openProjectCfg) => ({ openProjectCfg, currentTaskOrParent })),
          );
        }),
        filter(
          ({ openProjectCfg, currentTaskOrParent }) =>
            isOpenProjectEnabled(openProjectCfg) &&
            openProjectCfg.isTransitionIssuesEnabled,
        ),
        concatMap(({ openProjectCfg, currentTaskOrParent }) =>
          this._handleTransitionForIssue(
            IssueLocalState.IN_PROGRESS,
            openProjectCfg,
            currentTaskOrParent,
          ),
        ),
      ),
    { dispatch: false },
  );

  checkForDoneTransition$ = createEffect(
    () =>
      this._actions$.pipe(
        ofType(TaskSharedActions.updateTask),
        filter(({ task }): boolean => !!task.changes.isDone),
        // NOTE: as this is only a partial object we need to get the full one
        concatMap(({ task }) => this._taskService.getByIdOnce$(task.id.toString())),
        filter((task: Task) => task && task.issueType === OPEN_PROJECT_TYPE),
        concatMap((task: Task) => {
          if (!task.issueProviderId) {
            throw new Error('No issueProviderId for task');
          }
          return this._getCfgOnce$(task.issueProviderId).pipe(
            map((openProjectCfg) => ({ openProjectCfg, task })),
          );
        }),
        filter(
          ({ openProjectCfg, task }) =>
            isOpenProjectEnabled(openProjectCfg) &&
            openProjectCfg.isTransitionIssuesEnabled,
        ),
        concatMap(({ openProjectCfg, task }) => {
          return this._handleTransitionForIssue(
            IssueLocalState.DONE,
            openProjectCfg,
            task,
          );
        }),
      ),
    { dispatch: false },
  );

  private _openTrackTimeDialog(
    task: Task,
    workPackageId: number,
    openProjectCfg: OpenProjectCfg,
  ): void {
    this._openProjectApiService
      .getById$(workPackageId, openProjectCfg)
      .pipe(take(1))
      .subscribe(async (workPackage) => {
        const { DialogTrackTimeComponent } =
          await import('../../shared/dialog-track-time/dialog-track-time.component');
        const timeLogged = parseOpenProjectDuration(workPackage.spentTime);
        this._matDialog.open(DialogTrackTimeComponent, {
          restoreFocus: true,
          data: {
            task,
            issueIcon: 'open_project',
            issueLabel: `${workPackage.id} ${workPackage.subject}`,
            issueUrl: workPackage.url,
            timeLogged,
            activities$: this._openProjectApiService.getActivitiesForTrackTime$(
              workPackage.id,
              openProjectCfg,
            ),
            defaultTime: openProjectCfg.timeTrackingDialogDefaultTime,
            configTimeKey: 'timeTrackingDialogDefaultTime',
            onSubmit: (params: TrackTimeSubmitParams) =>
              this._openProjectApiService.trackTime$({
                workPackage,
                spentOn: getDbDateStr(params.started),
                hours: msToIsoDuration(params.timeSpent),
                comment: params.comment,
                activityId: params.activityId ?? 1,
                cfg: openProjectCfg,
              }),
            successMsg: T.F.OPEN_PROJECT.S.POST_TIME_SUCCESS,
            successTranslateParams: {
              issueTitle: formatOpenProjectWorkPackageSubjectForSnack(workPackage),
            },
            t: {
              title: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.TITLE,
              submitFor: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.SUBMIT_TIME_FOR,
              currentlyLogged: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.CURRENTLY_LOGGED,
              submit: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.POST_TIME,
              timeSpent: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.TIME_SPENT,
              timeSpentTooltip: T.F.JIRA.DIALOG_WORKLOG.TIME_SPENT_TOOLTIP,
              started: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.STARTED,
              invalidDate: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.INVALID_DATE,
              comment: T.G.COMMENT,
              activity: T.F.OPEN_PROJECT.DIALOG_TRACK_TIME.ACTIVITY,
            },
          },
        });
      });
  }

  private _getCfgOnce$(issueProviderId: string): Observable<OpenProjectCfg> {
    return this._issueProviderService.getCfgOnce$(issueProviderId, 'OPEN_PROJECT');
  }

  private _handleTransitionForIssue(
    localState: IssueLocalState,
    openProjectCfg: OpenProjectCfg,
    task: Task,
  ): Observable<unknown> {
    const chosenTransition: OpenProjectTransitionOption | undefined =
      openProjectCfg.transitionConfig?.[localState];

    if (!task.issueId) {
      throw new Error('No issueId for task');
    }

    switch (chosenTransition) {
      case 'DO_NOT':
        return EMPTY;
      case 'ALWAYS_ASK':
        return this._openProjectApiService
          .getById$(task.issueId as unknown as number, openProjectCfg)
          .pipe(
            concatMap((issue) => this._openTransitionDialog(issue, localState, task)),
          );
      default:
        if (!chosenTransition || !chosenTransition.id) {
          this._snackService.open({
            msg: T.F.JIRA.S.NO_VALID_TRANSITION,
            type: 'ERROR',
          });
          // NOTE: we would kill the whole effect chain if we do this
          // return throwError({[HANDLED_ERROR_PROP_STR]: 'Jira: No valid transition configured'});
          return timer(2000).pipe(
            concatMap(() =>
              this._openProjectApiService.getById$(
                task.issueId as unknown as number,
                openProjectCfg,
              ),
            ),
            concatMap((issue: OpenProjectWorkPackage) =>
              this._openTransitionDialog(issue, localState, task),
            ),
          );
        }

        return this._openProjectApiService
          .getById$(task.issueId as unknown as number, openProjectCfg)
          .pipe(
            concatMap((issue) => {
              if (
                !issue._embedded.status ||
                issue._embedded.status.name !== chosenTransition.name
              ) {
                return this._openProjectApiService
                  .transitionIssue$(
                    { ...issue, percentageDone: openProjectCfg.progressOnDone ?? 0 },
                    chosenTransition,
                    openProjectCfg,
                  )
                  .pipe(
                    concatMap(() => {
                      this._snackService.open({
                        type: 'SUCCESS',
                        msg: T.F.OPEN_PROJECT.S.TRANSITION_SUCCESS,
                        translateParams: {
                          issueKey: `${issue.subject}`,
                          chosenTransition: `${chosenTransition.name}`,
                        },
                      });
                      return this._issueService.refreshIssueTask(task, false, false);
                    }),
                  );
              } else {
                // no transition required
                return EMPTY;
              }
            }),
          );
    }
  }

  private _openTransitionDialog(
    issue: OpenProjectWorkPackage,
    localState: IssueLocalState,
    task: Task,
  ): Observable<unknown> {
    return from(
      import('./open-project-view-components/dialog-openproject-transition/dialog-open-project-transition.component'),
    ).pipe(
      switchMap(({ DialogOpenProjectTransitionComponent }) =>
        this._matDialog
          .open(DialogOpenProjectTransitionComponent, {
            restoreFocus: true,
            data: { issue, localState, task },
          })
          .afterClosed(),
      ),
    );
  }
}
