import { defineStore } from "pinia";
import type {
  FileTree,
  ResourceIn,
  ResourceOut,
  ResourceVersionIn,
  ResourceVersionOut,
  UserRequestAnswer,
  UserSynchronizationRequestIn,
  UserSynchronizationRequestOut,
} from "@/client/resource";
import {
  ResourceService,
  ResourceVersionService,
  Status,
} from "@/client/resource";
import { useAuthStore } from "@/stores/users";
import { useNameStore } from "@/stores/names";

function parseFileTree(parent: string, tree?: FileTree | null): string[] {
  if (tree == undefined || tree.contents == undefined) {
    return [];
  }
  let files: string[] = [];
  const currentName = parent.length > 0 ? parent + tree.name + "/" : "/";
  for (const dir of tree.contents) {
    files.push(currentName + dir.name);
    if (dir.type === "directory") {
      files = files.concat(parseFileTree(currentName, dir));
    }
  }
  return files;
}

export const useResourceStore = defineStore({
  id: "resources",
  state: () =>
    ({
      resourceMapping: {},
      ownResourceMapping: {},
      reviewableResourceMapping: {},
      syncRequestMapping: {},
      resourceTree: {},
      resourceTreeList: {},
      __syncRequestsFetched: false,
    }) as {
      resourceMapping: Record<string, ResourceOut>;
      ownResourceMapping: Record<string, ResourceOut>;
      reviewableResourceMapping: Record<string, ResourceOut>;
      syncRequestMapping: Record<string, UserSynchronizationRequestOut>;
      resourceTree: Record<string, FileTree | null>;
      resourceTreeList: Record<string, string[]>;
      __syncRequestsFetched: boolean;
    },
  getters: {
    resources(): ResourceOut[] {
      return Object.values(this.resourceMapping);
    },
    versionMapping(): Record<string, ResourceVersionOut> {
      const mapping: Record<string, ResourceVersionOut> = {};
      for (const resource of this.resources) {
        for (const version of resource.versions) {
          mapping[version.resource_version_id] = version;
        }
      }
      return mapping;
    },
    getLatestVersion(): (resource_id: string) => string {
      return (resource_id) => {
        for (const version of this.resourceMapping[resource_id]?.versions ??
          []) {
          if (version.status === Status.LATEST) {
            return version.resource_version_id;
          }
        }
        return "";
      };
    },
    ownResources(): ResourceOut[] {
      return Object.values(this.ownResourceMapping);
    },
    reviewableResources(): ResourceOut[] {
      return Object.values(this.reviewableResourceMapping);
    },
    syncRequests(): UserSynchronizationRequestOut[] {
      return Object.values(this.syncRequestMapping);
    },
  },
  actions: {
    fetchResource(
      resource_id: string,
      versionStatus?: Status[],
    ): Promise<ResourceOut> {
      return ResourceService.resourceGetResource(
        resource_id,
        versionStatus,
      ).then((resource) => {
        const nameStore = useNameStore();
        nameStore.addNameToMapping(resource.resource_id, resource.name);
        for (const version of resource.versions) {
          nameStore.addNameToMapping(
            version.resource_version_id,
            version.release,
          );
        }
        return resource;
      });
    },
    fetchSyncRequests(
      onFinally?: () => void,
    ): Promise<UserSynchronizationRequestOut[]> {
      if (this.__syncRequestsFetched) {
        onFinally?.();
      }
      return ResourceService.resourceListSyncRequests()
        .then((requests) => {
          this.__syncRequestsFetched = true;
          const newMapping: Record<string, UserSynchronizationRequestOut> = {};
          for (const request of requests) {
            newMapping[request.resource_version_id] = request;
          }
          this.syncRequestMapping = newMapping;
          return requests;
        })
        .finally(onFinally);
    },
    fetchResources(
      maintainerId?: string,
      versionStatus?: Status[],
      searchString?: string,
      _public?: boolean,
    ): Promise<ResourceOut[]> {
      return ResourceService.resourceListResources(
        maintainerId,
        versionStatus,
        searchString,
        _public,
      ).then((resources) => {
        const nameStore = useNameStore();
        for (const resource of resources) {
          nameStore.addNameToMapping(resource.resource_id, resource.name);
          for (const version of resource.versions) {
            nameStore.addNameToMapping(
              version.resource_version_id,
              version.release,
            );
          }
        }
        return resources;
      });
    },
    fetchResourceTree(
      resource_id: string,
      resource_version_id: string,
      onFinally?: () => void,
    ): Promise<FileTree> {
      if (this.resourceTree[resource_version_id] === null) {
        onFinally?.();
      }
      return ResourceVersionService.resourceVersionResourceFileTree(
        resource_version_id,
        resource_id,
      )
        .then((tree) => {
          this.resourceTree[resource_version_id] = tree[0];
          this.resourceTreeList[resource_version_id] = parseFileTree(
            "",
            tree[0],
          );
          return tree;
        })
        .catch((err) => {
          this.resourceTree[resource_version_id] = null;
          return err;
        })
        .finally(onFinally);
    },
    fetchReviewableResources(onFinally?: () => void): Promise<ResourceOut[]> {
      if (Object.keys(this.reviewableResourceMapping).length > 0) {
        onFinally?.();
      }
      return this.fetchResources(undefined, [Status.WAIT_FOR_REVIEW])
        .then((resources) => {
          const newMapping: Record<string, ResourceOut> = {};
          for (const resource of resources) {
            newMapping[resource.resource_id] = resource;
          }
          this.reviewableResourceMapping = newMapping;
          return resources;
        })
        .finally(onFinally);
    },
    fetchPublicResources(onFinally?: () => void): Promise<ResourceOut[]> {
      if (this.resources.length > 0) {
        onFinally?.();
      }
      return this.fetchResources(undefined, [
        Status.APPROVED,
        Status.SYNC_REQUESTED,
        Status.SYNCHRONIZING,
        Status.SYNC_ERROR,
        Status.SYNCHRONIZED,
        Status.SETTING_LATEST,
        Status.LATEST,
        Status.CLUSTER_DELETE_ERROR,
      ])
        .then((resources) => {
          const newMapping: Record<string, ResourceOut> = {};
          for (const resource of resources) {
            newMapping[resource.resource_id] = resource;
          }
          this.resourceMapping = newMapping;
          return resources;
        })
        .finally(onFinally);
    },
    fetchOwnResource(
      resource_id: string,
      onFinally?: () => void,
    ): Promise<ResourceOut> {
      if (this.ownResourceMapping[resource_id]) {
        onFinally?.();
      }
      return this.fetchResource(resource_id)
        .then((resource) => {
          this.ownResourceMapping[resource.resource_id] = resource;
          return resource;
        })
        .finally(onFinally);
    },
    fetchOwnResources(onFinally?: () => void): Promise<ResourceOut[]> {
      const authStore = useAuthStore();
      if (this.ownResources.length > 0) {
        onFinally?.();
      }
      return this.fetchResources(authStore.currentUID)
        .then((resources) => {
          const newMapping: Record<string, ResourceOut> = {};
          for (const resource of resources) {
            newMapping[resource.resource_id] = resource;
          }
          this.ownResourceMapping = newMapping;
          return resources;
        })
        .finally(onFinally);
    },
    async createResource(resource: ResourceIn): Promise<ResourceOut> {
      const createdResource =
        await ResourceService.resourceCreateResource(resource);
      this.ownResourceMapping[createdResource.resource_id] = createdResource;
      const nameStore = useNameStore();
      nameStore.addNameToMapping(
        createdResource.resource_id,
        createdResource.name,
      );
      nameStore.addNameToMapping(
        createdResource.versions[0].resource_version_id,
        createdResource.versions[0].release,
      );

      return createdResource;
    },
    requestSynchronization(
      resourceVersion: ResourceVersionOut,
      request: UserSynchronizationRequestIn,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionRequestResourceVersionSync(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
        request,
      )
        .then((changedResourceVersion) => {
          const versionIndex = this.resourceMapping[
            changedResourceVersion.resource_id
          ]?.versions?.findIndex(
            (version) =>
              version.resource_version_id ==
              changedResourceVersion.resource_version_id,
          );
          if (versionIndex != undefined && versionIndex > -1) {
            this.resourceMapping[changedResourceVersion.resource_id].versions[
              versionIndex
            ] = changedResourceVersion;
          }
          return changedResourceVersion;
        })
        .then((changedResourceVersion) => {
          const versionIndex = this.ownResourceMapping[
            changedResourceVersion.resource_id
          ]?.versions?.findIndex(
            (version) =>
              version.resource_version_id ==
              changedResourceVersion.resource_version_id,
          );
          if (versionIndex != undefined && versionIndex > -1) {
            this.ownResourceMapping[
              changedResourceVersion.resource_id
            ].versions[versionIndex] = changedResourceVersion;
          }
          return changedResourceVersion;
        });
    },
    requestReview(
      resourceVersion: ResourceVersionOut,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionRequestResourceVersionReview(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
      ).then((changedResourceVersion) => {
        if (
          this.ownResourceMapping[changedResourceVersion.resource_id] ==
          undefined
        ) {
          this.fetchOwnResource(resourceVersion.resource_id);
          return changedResourceVersion;
        }
        const versionIndex = this.ownResourceMapping[
          changedResourceVersion.resource_id
        ].versions.findIndex(
          (version) =>
            version.resource_version_id ==
            changedResourceVersion.resource_version_id,
        );
        if (versionIndex > -1) {
          this.ownResourceMapping[changedResourceVersion.resource_id].versions[
            versionIndex
          ] = changedResourceVersion;
        } else {
          this.ownResourceMapping[
            changedResourceVersion.resource_id
          ].versions.push(changedResourceVersion);
        }
        return changedResourceVersion;
      });
    },
    updateResource(
      resource_id: string,
      version: ResourceVersionIn,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionRequestResourceVersion(
        resource_id,
        version,
      ).then((versionOut) => {
        if (this.ownResourceMapping[versionOut.resource_id] == undefined) {
          this.fetchOwnResource(versionOut.resource_id);
          return versionOut;
        }
        useNameStore().addNameToMapping(
          versionOut.resource_version_id,
          versionOut.release,
        );
        this.ownResourceMapping[versionOut.resource_id].versions.push(
          versionOut,
        );
        return versionOut;
      });
    },
    reviewResource(
      resourceVersion: ResourceVersionOut,
      requestAnswer: UserRequestAnswer,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionResourceVersionReview(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
        requestAnswer,
      ).then(this._updateReviewableResourceVersion);
    },
    syncResource(
      resourceVersion: ResourceVersionOut,
      requestAnswer: UserRequestAnswer,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionResourceVersionSync(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
        requestAnswer,
      ).then((version) => {
        delete this.syncRequestMapping[version.resource_version_id];
        return version;
      });
    },
    _updateReviewableResourceVersion(
      version: ResourceVersionOut,
    ): ResourceVersionOut {
      if (this.reviewableResourceMapping[version.resource_id] == undefined) {
        return version;
      }
      const versionIndex = this.reviewableResourceMapping[
        version.resource_id
      ].versions.findIndex(
        (version) => version.resource_version_id == version.resource_version_id,
      );
      if (versionIndex > -1) {
        this.reviewableResourceMapping[version.resource_id].versions[
          versionIndex
        ] = version;
      } else {
        this.reviewableResourceMapping[version.resource_id].versions.push(
          version,
        );
      }
      return version;
    },
    setLatestResource(
      resourceVersion: ResourceVersionOut,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionResourceVersionLatest(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
      );
    },
    deleteOnCluster(
      resourceVersion: ResourceVersionOut,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionDeleteResourceVersionCluster(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
      );
    },
    deleteInS3(
      resourceVersion: ResourceVersionOut,
    ): Promise<ResourceVersionOut> {
      return ResourceVersionService.resourceVersionDeleteResourceVersionS3(
        resourceVersion.resource_id,
        resourceVersion.resource_version_id,
      );
    },
  },
});