/**
 * Copyright (c) 2023-present Pi Dash Software, Inc. and contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 * See the LICENSE file for details.
 */

import { makeObservable, observable } from "mobx";
import { computedFn } from "mobx-utils";
// types
import type { TIssue, TIssueServiceType } from "@pi-dash/types";
import { EIssueServiceType } from "@pi-dash/types";
// services
import { IssueArchiveService, WorkspaceDraftService, IssueService } from "@/services/issue";
// types
import type { IIssueDetail } from "./root.store";

export type TFetchIssueOptions = {
  preserveSubscription?: boolean;
  skipActivityAndComments?: boolean;
};

export type TFetchIssueWithIdentifierOptions = {
  lite?: boolean;
};

export type TIssueLite = Pick<
  TIssue,
  | "id"
  | "sequence_id"
  | "name"
  | "sort_order"
  | "project_id"
  | "created_at"
  | "updated_at"
  | "created_by"
  | "updated_by"
  | "is_draft"
  | "is_epic"
  | "archived_at"
  | "is_intake"
  | "is_synced"
> & {
  description_html: string;
};

export type TFetchIssueWithIdentifier = {
  (
    workspaceSlug: string,
    project_identifier: string,
    sequence_id: string,
    options: { lite: true }
  ): Promise<TIssueLite>;
  (
    workspaceSlug: string,
    project_identifier: string,
    sequence_id: string,
    options?: TFetchIssueWithIdentifierOptions
  ): Promise<TIssue>;
};

export interface IIssueStoreActions {
  // actions
  fetchIssue: (
    workspaceSlug: string,
    projectId: string,
    issueId: string,
    options?: TFetchIssueOptions
  ) => Promise<TIssue>;
  updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
  removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
  archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
  addCycleToIssue: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
  addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<void>;
  removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
  changeModulesInIssue: (
    workspaceSlug: string,
    projectId: string,
    issueId: string,
    addModuleIds: string[],
    removeModuleIds: string[]
  ) => Promise<void>;
  removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise<void>;
  fetchIssueWithIdentifier: TFetchIssueWithIdentifier;
}

export interface IIssueStore extends IIssueStoreActions {
  getIsFetchingIssueDetails: (issueId: string | undefined) => boolean;
  // helper methods
  getIssueById: (issueId: string) => TIssue | undefined;
  getIssueIdByIdentifier: (issueIdentifier: string) => string | undefined;
}

export class IssueStore implements IIssueStore {
  fetchingIssueDetails: string | undefined = undefined;
  // root store
  rootIssueDetailStore: IIssueDetail;
  // services
  serviceType;
  issueService;
  epicService;
  issueArchiveService;
  draftWorkItemService;

  constructor(rootStore: IIssueDetail, serviceType: TIssueServiceType) {
    makeObservable(this, {
      fetchingIssueDetails: observable.ref,
    });
    // root store
    this.rootIssueDetailStore = rootStore;
    // services
    this.serviceType = serviceType;
    this.issueService = new IssueService(serviceType);
    this.epicService = new IssueService(EIssueServiceType.EPICS);
    this.issueArchiveService = new IssueArchiveService(serviceType);
    this.draftWorkItemService = new WorkspaceDraftService();
  }

  getIsFetchingIssueDetails = computedFn((issueId: string | undefined) => {
    if (!issueId) return false;

    return this.fetchingIssueDetails === issueId;
  });

  // helper methods
  getIssueById = computedFn((issueId: string) => {
    if (!issueId) return undefined;
    return this.rootIssueDetailStore.rootIssueStore.issues.getIssueById(issueId) ?? undefined;
  });

  getIssueIdByIdentifier = computedFn((issueIdentifier: string) => {
    if (!issueIdentifier) return undefined;
    return this.rootIssueDetailStore.rootIssueStore.issues.getIssueIdByIdentifier(issueIdentifier) ?? undefined;
  });

  // actions
  fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, options: TFetchIssueOptions = {}) => {
    const query = {
      expand: "issue_reactions,issue_attachments,issue_link,parent",
    };

    this.fetchingIssueDetails = issueId;
    const issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);

    if (!issue) throw new Error("Work item not found");

    this.addIssueToStore(issue);

    // store handlers from issue detail
    // parent
    if (issue && issue?.parent && issue?.parent?.id && issue?.parent?.project_id) {
      this.issueService
        .retrieve(workspaceSlug, issue.parent.project_id, issue?.parent?.id)
        .then((res) => this.rootIssueDetailStore.rootIssueStore.issues.addIssue([res]));
    }
    // assignees
    // labels
    // state

    // issue reactions
    if (issue.issue_reactions) this.rootIssueDetailStore.addReactions(issueId, issue.issue_reactions);

    // fetch issue links
    if (issue.issue_link) this.rootIssueDetailStore.addLinks(issueId, issue.issue_link);

    // fetch issue attachments
    if (issue.issue_attachments) this.rootIssueDetailStore.addAttachments(issueId, issue.issue_attachments);

    if (
      !options.preserveSubscription ||
      this.rootIssueDetailStore.subscription.getSubscriptionByIssueId(issueId) === undefined
    ) {
      this.rootIssueDetailStore.addSubscription(issueId, issue.is_subscribed);
    }

    if (!options.skipActivityAndComments) {
      // fetch issue activity
      this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);

      // fetch issue comments
      this.rootIssueDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId);
    }

    // fetch sub issues
    this.rootIssueDetailStore.subIssues.fetchSubIssues(workspaceSlug, projectId, issueId);

    // fetch issue relations
    this.rootIssueDetailStore.relation.fetchRelations(workspaceSlug, projectId, issueId);

    // fetching states
    // TODO: check if this function is required
    this.rootIssueDetailStore.rootIssueStore.rootStore.state.fetchProjectStates(workspaceSlug, projectId);

    return issue;
  };

  addIssueToStore = (issue: TIssue) => {
    const issuePayload: TIssue = {
      id: issue?.id,
      sequence_id: issue?.sequence_id,
      name: issue?.name,
      description_html: issue?.description_html,
      sort_order: issue?.sort_order,
      state_id: issue?.state_id,
      priority: issue?.priority,
      label_ids: issue?.label_ids,
      assignee_ids: issue?.assignee_ids,
      estimate_point: issue?.estimate_point,
      sub_issues_count: issue?.sub_issues_count,
      attachment_count: issue?.attachment_count,
      link_count: issue?.link_count,
      project_id: issue?.project_id,
      parent_id: issue?.parent_id,
      cycle_id: issue?.cycle_id,
      module_ids: issue?.module_ids,
      type_id: issue?.type_id,
      created_at: issue?.created_at,
      updated_at: issue?.updated_at,
      start_date: issue?.start_date,
      target_date: issue?.target_date,
      completed_at: issue?.completed_at,
      archived_at: issue?.archived_at,
      created_by: issue?.created_by,
      updated_by: issue?.updated_by,
      is_draft: issue?.is_draft,
      is_subscribed: issue?.is_subscribed,
      is_epic: issue?.is_epic,
      is_intake: issue?.is_intake,
      is_synced: issue?.is_synced,
      git_work_branch: issue?.git_work_branch,
      agent_ticker: issue?.agent_ticker,
      agent_status: issue?.agent_status,
      state__group: issue?.state__group,
    };

    const compactIssuePayload = Object.fromEntries(
      Object.entries(issuePayload).filter(([, value]) => value !== undefined)
    ) as TIssue;

    this.rootIssueDetailStore.rootIssueStore.issues.addIssue([compactIssuePayload]);
    this.fetchingIssueDetails = undefined;

    return compactIssuePayload;
  };

  updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
    const currentStore =
      this.serviceType === EIssueServiceType.EPICS
        ? this.rootIssueDetailStore.rootIssueStore.projectEpics
        : this.rootIssueDetailStore.rootIssueStore.projectIssues;

    await Promise.all([
      currentStore.updateIssue(workspaceSlug, projectId, issueId, data),
      this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId),
    ]);

    if ("state_id" in data) await this.fetchIssue(workspaceSlug, projectId, issueId);
  };

  removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
    const currentStore =
      this.serviceType === EIssueServiceType.EPICS
        ? this.rootIssueDetailStore.rootIssueStore.projectEpics
        : this.rootIssueDetailStore.rootIssueStore.projectIssues;
    currentStore.removeIssue(workspaceSlug, projectId, issueId);
  };

  archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
    const currentStore =
      this.serviceType === EIssueServiceType.EPICS
        ? this.rootIssueDetailStore.rootIssueStore.projectEpics
        : this.rootIssueDetailStore.rootIssueStore.projectIssues;
    currentStore.archiveIssue(workspaceSlug, projectId, issueId);
  };

  addCycleToIssue = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
    await this.rootIssueDetailStore.rootIssueStore.cycleIssues.addCycleToIssue(
      workspaceSlug,
      projectId,
      cycleId,
      issueId
    );
    await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
  };

  addIssueToCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
    await this.rootIssueDetailStore.rootIssueStore.cycleIssues.addIssueToCycle(
      workspaceSlug,
      projectId,
      cycleId,
      issueIds,
      false
    );
    if (issueIds && issueIds.length > 0)
      await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueIds[0]);
  };

  removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
    const cycle = await this.rootIssueDetailStore.rootIssueStore.cycleIssues.removeIssueFromCycle(
      workspaceSlug,
      projectId,
      cycleId,
      issueId
    );
    await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
    return cycle;
  };

  changeModulesInIssue = async (
    workspaceSlug: string,
    projectId: string,
    issueId: string,
    addModuleIds: string[],
    removeModuleIds: string[]
  ) => {
    await this.rootIssueDetailStore.rootIssueStore.moduleIssues.changeModulesInIssue(
      workspaceSlug,
      projectId,
      issueId,
      addModuleIds,
      removeModuleIds
    );
    await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
  };

  removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
    const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.removeIssuesFromModule(
      workspaceSlug,
      projectId,
      moduleId,
      [issueId]
    );
    await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
    return currentModule;
  };

  fetchIssueWithIdentifier = (async (
    workspaceSlug: string,
    project_identifier: string,
    sequence_id: string,
    options: TFetchIssueWithIdentifierOptions = {}
  ): Promise<TIssue | TIssueLite> => {
    const query = options.lite
      ? { lite: true }
      : {
          expand: "issue_reactions,issue_attachments,issue_link,parent",
        };
    const issue = await this.issueService.retrieveWithIdentifier(workspaceSlug, project_identifier, sequence_id, query);
    const issueIdentifier = `${project_identifier}-${sequence_id}`;
    const issueId = issue?.id;
    const projectId = issue?.project_id;
    const rootWorkItemDetailStore = issue?.is_epic
      ? this.rootIssueDetailStore.rootIssueStore.epicDetail
      : this.rootIssueDetailStore.rootIssueStore.issueDetail;

    if (!issue || !projectId || !issueId) throw new Error("Issue not found");

    this.addIssueToStore(issue);

    // add identifiers to map
    rootWorkItemDetailStore.rootIssueStore.issues.addIssueIdentifier(issueIdentifier, issueId);

    if (issue.is_subscribed !== undefined) rootWorkItemDetailStore.addSubscription(issueId, issue.is_subscribed);

    if (options.lite) {
      if (!issue.is_intake) {
        // start the visible activity/comment stream as soon as the core issue is known
        rootWorkItemDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
        rootWorkItemDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId);
      }
      return issue as TIssueLite;
    }

    // handle parent issue if exists
    if (issue?.parent && issue?.parent?.id && issue?.parent?.project_id) {
      this.issueService
        .retrieve(workspaceSlug, issue.parent.project_id, issue.parent.id)
        .then((res) => this.rootIssueDetailStore.rootIssueStore.issues.addIssue([res]));
    }

    // add related data
    if (issue.issue_reactions) rootWorkItemDetailStore.addReactions(issue.id, issue.issue_reactions);
    if (issue.issue_link) rootWorkItemDetailStore.addLinks(issue.id, issue.issue_link);
    if (issue.issue_attachments) rootWorkItemDetailStore.addAttachments(issue.id, issue.issue_attachments);

    // fetch sub issues
    rootWorkItemDetailStore.subIssues.fetchSubIssues(workspaceSlug, projectId, issueId);

    // fetch issue relations
    rootWorkItemDetailStore.relation.fetchRelations(workspaceSlug, projectId, issueId);

    // fetch issue activity
    rootWorkItemDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);

    // fetch issue comments
    rootWorkItemDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId);

    // fetching states
    // TODO: check if this function is required
    rootWorkItemDetailStore.rootIssueStore.rootStore.state.fetchProjectStates(workspaceSlug, projectId);

    return issue;
  }) as TFetchIssueWithIdentifier;
}
