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

import { set } from "lodash-es";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import type {
  TExternalContourRequest,
  TExternalContourTargetOptions,
  TExternalContourTargetProject,
  TInboxIssueCurrentTab,
  TIssue,
} from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { ExternalContourService } from "@/services/external-contours";
import type { CoreRootStore } from "../root.store";

type TLoader = "init-loading" | "mutation-loading" | "issue-loading" | undefined;

export interface IProjectExternalContoursStore {
  currentProjectId: string;
  currentTab: TInboxIssueCurrentTab;
  error: { message: string; status: "init-error" } | undefined;
  loader: TLoader;
  requestIds: string[];
  requests: Record<string, TExternalContourRequest>;
  targetProjectIds: string[];
  targetProjects: Record<string, TExternalContourTargetProject>;
  targetOptionsMap: Record<string, TExternalContourTargetOptions>;
  createRequest: (
    workspaceSlug: string,
    projectId: string,
    data: Partial<TIssue> & { target_project_id?: string | null }
  ) => Promise<TExternalContourRequest | undefined>;
  updateRequest: (
    workspaceSlug: string,
    projectId: string,
    requestId: string,
    data: Pick<Partial<TIssue>, "name" | "description_html">
  ) => Promise<TExternalContourRequest | undefined>;
  decideRequest: (
    workspaceSlug: string,
    projectId: string,
    requestId: string,
    action: "accept" | "decline",
    comment?: string
  ) => Promise<TExternalContourRequest | undefined>;
  replyToRequest: (
    workspaceSlug: string,
    projectId: string,
    requestId: string,
    comment: string
  ) => Promise<TExternalContourRequest | undefined>;
  fetchTargetProjects: (workspaceSlug: string, projectId: string) => Promise<void>;
  fetchTargetOptions: (workspaceSlug: string, projectId: string, targetProjectId: string) => Promise<TExternalContourTargetOptions | undefined>;
  fetchRequestById: (workspaceSlug: string, projectId: string, requestId: string) => Promise<TExternalContourRequest | undefined>;
  fetchRequests: (workspaceSlug: string, projectId: string, tab?: TInboxIssueCurrentTab) => Promise<void>;
  getIsRequestAvailable: (requestId: string) => boolean;
  getRequestById: (requestId: string) => TExternalContourRequest | undefined;
  getTargetProjectById: (projectId: string | null | undefined) => TExternalContourTargetProject | undefined;
  getTargetOptionsByProjectId: (projectId: string | null | undefined) => TExternalContourTargetOptions | undefined;
  handleCurrentTab: (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => Promise<void>;
  openRequestIds: string[];
  closedRequestIds: string[];
  filteredRequestIds: string[];
  upsertRequests: (requests: TExternalContourRequest[]) => void;
  updateRequestIssue: (requestId: string, issueData: Partial<TExternalContourRequest["issue"]>) => void;
}

export class ProjectExternalContoursStore implements IProjectExternalContoursStore {
  currentProjectId = "";
  currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
  error: { message: string; status: "init-error" } | undefined = undefined;
  loader: TLoader = "init-loading";
  requestIds: string[] = [];
  requests: Record<string, TExternalContourRequest> = {};
  targetProjectIds: string[] = [];
  targetProjects: Record<string, TExternalContourTargetProject> = {};
  targetOptionsMap: Record<string, TExternalContourTargetOptions> = {};

  externalContourService;

  constructor(_store: CoreRootStore) {
    makeObservable(this, {
      currentProjectId: observable.ref,
      currentTab: observable.ref,
      error: observable.ref,
      loader: observable.ref,
      requestIds: observable.shallow,
      requests: observable,
      targetProjectIds: observable.shallow,
      targetProjects: observable,
      targetOptionsMap: observable,
      openRequestIds: computed,
      closedRequestIds: computed,
      filteredRequestIds: computed,
      fetchTargetProjects: action,
      fetchTargetOptions: action,
      fetchRequests: action,
      fetchRequestById: action,
      createRequest: action,
      updateRequest: action,
      decideRequest: action,
      replyToRequest: action,
      handleCurrentTab: action,
      upsertRequests: action,
      updateRequestIssue: action,
    });

    this.externalContourService = new ExternalContourService();
  }

  get openRequestIds() {
    return this.requestIds.filter((requestId) => this.requests[requestId]?.status === "open");
  }

  get closedRequestIds() {
    return this.requestIds.filter((requestId) => this.requests[requestId]?.status === "closed");
  }

  get filteredRequestIds() {
    return this.currentTab === EInboxIssueCurrentTab.CLOSED ? this.closedRequestIds : this.openRequestIds;
  }

  getRequestById = (requestId: string) => this.requests[requestId];
  getTargetProjectById = (projectId: string | null | undefined) => (projectId ? this.targetProjects[projectId] : undefined);
  getTargetOptionsByProjectId = (projectId: string | null | undefined) =>
    projectId ? this.targetOptionsMap[projectId] : undefined;

  getIsRequestAvailable = (requestId: string) => this.requestIds.includes(requestId);

  upsertRequests = (requests: TExternalContourRequest[]) => {
    requests.forEach((request) => {
      set(this.requests, request.id, request);
      if (!this.requestIds.includes(request.id)) this.requestIds.push(request.id);
    });
    this.requestIds = this.requestIds.sort((left, right) => {
      const leftUpdatedAt = this.requests[left]?.updated_at || "";
      const rightUpdatedAt = this.requests[right]?.updated_at || "";
      return rightUpdatedAt.localeCompare(leftUpdatedAt);
    });
  };

  fetchTargetProjects = async (workspaceSlug: string, projectId: string) => {
    try {
      const projects = await this.externalContourService.listTargetProjects(workspaceSlug, projectId);
      runInAction(() => {
        this.targetProjectIds = [];
        this.targetProjects = {};
        projects.forEach((project) => {
          set(this.targetProjects, project.id, project);
          this.targetProjectIds.push(project.id);
        });
      });
    } catch (error) {
      runInAction(() => {
        this.targetProjectIds = [];
        this.targetProjects = {};
      });
      throw error;
    }
  };

  fetchTargetOptions = async (workspaceSlug: string, projectId: string, targetProjectId: string) => {
    try {
      const options = await this.externalContourService.retrieveTargetOptions(workspaceSlug, projectId, targetProjectId);
      runInAction(() => {
        set(this.targetOptionsMap, targetProjectId, options);
        set(this.targetProjects, targetProjectId, options.project);
        if (!this.targetProjectIds.includes(targetProjectId)) this.targetProjectIds.push(targetProjectId);
      });
      return options;
    } catch (error) {
      throw error;
    }
  };

  handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => {
    this.currentProjectId = projectId;
    this.requestIds = [];
    this.requests = {};
    this.error = undefined;
    this.loader = "init-loading";
    this.currentTab = tab;
    await this.fetchRequests(workspaceSlug, projectId, tab);
  };

  fetchRequests = async (workspaceSlug: string, projectId: string, tab = this.currentTab) => {
    this.loader = "init-loading";
    this.error = undefined;
    this.currentProjectId = projectId;
    this.currentTab = tab;

    try {
      const response = await this.externalContourService.list(workspaceSlug, projectId);
      runInAction(() => {
        this.requestIds = [];
        this.requests = {};
        this.upsertRequests(response.results || []);
        this.loader = undefined;
      });
    } catch (error: any) {
      runInAction(() => {
        this.loader = undefined;
        this.error = {
          message: error?.error || "Не удалось загрузить внешние контуры",
          status: "init-error",
        };
      });
    }
  };

  fetchRequestById = async (workspaceSlug: string, projectId: string, requestId: string) => {
    this.loader = "issue-loading";
    try {
      const request = await this.externalContourService.retrieve(workspaceSlug, projectId, requestId);
      runInAction(() => {
        this.upsertRequests([request]);
        this.loader = undefined;
      });
      return request;
    } catch (error) {
      runInAction(() => {
        this.loader = undefined;
      });
      return undefined;
    }
  };

  createRequest = async (workspaceSlug: string, projectId: string, data: Partial<TIssue> & { target_project_id?: string | null }) => {
    this.loader = "mutation-loading";
    try {
      const request = await this.externalContourService.create(workspaceSlug, projectId, data);
      runInAction(() => {
        this.upsertRequests([request]);
        this.currentTab = EInboxIssueCurrentTab.OPEN;
        this.loader = undefined;
      });
      return request;
    } catch (error) {
      runInAction(() => {
        this.loader = undefined;
      });
      throw error;
    }
  };

  updateRequest = async (
    workspaceSlug: string,
    projectId: string,
    requestId: string,
    data: Pick<Partial<TIssue>, "name" | "description_html">
  ) => {
    this.loader = "mutation-loading";
    try {
      const request = await this.externalContourService.updateRequest(workspaceSlug, projectId, requestId, data);
      runInAction(() => {
        this.upsertRequests([request]);
        this.loader = undefined;
      });
      return request;
    } catch (error) {
      runInAction(() => {
        this.loader = undefined;
      });
      throw error;
    }
  };

  decideRequest = async (
    workspaceSlug: string,
    projectId: string,
    requestId: string,
    action: "accept" | "decline",
    comment?: string
  ) => {
    this.loader = "mutation-loading";
    try {
      const request = await this.externalContourService.decide(workspaceSlug, projectId, requestId, action, comment);
      runInAction(() => {
        this.upsertRequests([request]);
        this.loader = undefined;
      });
      return request;
    } catch (error) {
      runInAction(() => {
        this.loader = undefined;
      });
      throw error;
    }
  };

  replyToRequest = async (workspaceSlug: string, projectId: string, requestId: string, comment: string) => {
    this.loader = "mutation-loading";
    try {
      const request = await this.externalContourService.reply(workspaceSlug, projectId, requestId, comment);
      runInAction(() => {
        this.upsertRequests([request]);
        this.loader = undefined;
      });
      return request;
    } catch (error) {
      runInAction(() => {
        this.loader = undefined;
      });
      throw error;
    }
  };

  updateRequestIssue = (requestId: string, issueData: Partial<TExternalContourRequest["issue"]>) => {
    if (!this.requests[requestId]) return;
    const nextStatus =
      issueData.state_detail?.group !== undefined
        ? ["completed", "cancelled"].includes(issueData.state_detail.group)
          ? "closed"
          : "open"
        : this.requests[requestId].status;
    this.requests[requestId] = {
      ...this.requests[requestId],
      issue: {
        ...this.requests[requestId].issue,
        ...issueData,
      },
      status: nextStatus,
    };
  };
}
