import { CogniteClient as CogniteClientV6 } from '@cognite/sdk-v6';
import { CogniteClient } from '@cognite/sdk';
import type { QueryRequest, Query } from './codegen';
import type { ModelNodeMap } from './schema';
import { RelationshipMap } from './schema';
/// <reference path='./codegen.d.ts'/>
import { generateQueryOp } from './codegen.esm.js';
import { FieldsSelection } from '@genql/runtime';

const getTypeKey = (typeName: string, version: number | string) =>
  `${typeName}_${version}`;

const getRelationshipKey = (
  typeName: string,
  attr: string,
  version: number | string
) => `${typeName}_${attr}_${version}`;

const CDF_ALPHA_VERSION_HEADERS = { 'cdf-version': 'alpha' };

export class FDMQueryClient {
  private _client: CogniteClient | CogniteClientV6;

  constructor(client: CogniteClient | CogniteClientV6) {
    this._client = client;
  }
  public runQuery = async <R extends QueryRequest>(
    query: R & { __name?: string }
  ) => {
    const response = await this._client.post<{
      data: FieldsSelection<Query, R>;
    }>(
      `/api/v1/projects/${PROJECT}/schema/api/${DM_NAME}/${DM_VERSION}/graphql`,
      { data: generateQueryOp(query) }
    );
    return response;
  };

  public async upsertNodes<T extends keyof ModelNodeMap>(
    key: T,
    items: ModelNodeMap[T][]
  ): Promise<ModelNodeMap[T][]> {
    const data = {
      items: items.map((item) =>
        Object.keys(item).reduce((prev, curKey) => {
          const key = curKey as keyof typeof item;
          const currKeyValue = item[key];
          if (isNodeRef(currKeyValue)) {
            const curNodeRef = currKeyValue as NodeRef;
            return {
              ...prev,
              [curKey]: curNodeRef.buildValue(DM_NAME),
            };
          }
          return { ...prev, [curKey]: item[key] };
        }, {})
      ),
      spaceExternalId: DM_NAME,
      model: [DM_NAME, getTypeKey(key, DM_VERSION)],
      overwrite: true,
    };

    const res = await this._client.post<{ items: ModelNodeMap[T][] }>(
      `/api/v1/projects/${this._client.project}/datamodelstorage/nodes`,
      {
        data,
        headers: CDF_ALPHA_VERSION_HEADERS,
        responseType: 'json',
        withCredentials: true,
      }
    );

    return res.data.items;
  }

  public async upsertRelationships<
    I extends { startNode: NodeRef; endNode: NodeRef; externalId?: string },
    K extends keyof typeof RelationshipMap
  >(
    type: K,
    property: keyof typeof RelationshipMap[K],
    items: I[]
  ): Promise<I[]> {
    const relationshipKey = getRelationshipKey(
      type,
      // @ts-ignore
      property,
      DM_VERSION
    );
    const data = {
      items: items.map((el) => ({
        startNode: el.startNode.buildValue(DM_NAME),
        endNode: el.endNode.buildValue(DM_NAME),
        type: [DM_NAME, relationshipKey],
        dummy: '',
        externalId:
          el.externalId ||
          `${el.startNode.externalId}_${el.endNode.externalId}`,
      })),
      spaceExternalId: DM_NAME,
      model: [DM_NAME, relationshipKey],
      overwrite: true,
      autoCreateStartNodes: true,
      autoCreateEndNodes: true,
    };

    const res = await this._client.post<{ items: I[] }>(
      `/api/v1/projects/${this._client.project}/datamodelstorage/edges`,
      {
        data,
        headers: CDF_ALPHA_VERSION_HEADERS,
        responseType: 'json',
        withCredentials: true,
      }
    );

    return res.data.items;
  }
  public async deleteNodes<T extends keyof ModelNodeMap>(
    _: T,
    items: string[]
  ): Promise<void> {
    const data = {
      items: items.map((item) => {
        ({
          externalId: item,
        });
      }),
      spaceExternalId: DM_NAME,
      overwrite: true,
    };

    const res = await this._client.post(
      `/api/v1/projects/${this._client.project}/datamodelstorage/nodes/delete`,
      {
        data,
        headers: CDF_ALPHA_VERSION_HEADERS,
        responseType: 'json',
        withCredentials: true,
      }
    );

    return;
  }

  public async deleteRelationships<K extends keyof typeof RelationshipMap>(
    _: K,
    _2: keyof typeof RelationshipMap[K],
    items: ({ startNode: NodeRef; endNode: NodeRef } | { externalId: string })[]
  ): Promise<void> {
    const data = {
      items: items.map((el) => ({
        externalId:
          'externalId' in el
            ? el.externalId
            : `${el.startNode.externalId}_${el.endNode.externalId}`,
      })),
      spaceExternalId: DM_NAME,
    };

    const res = await this._client.post(
      `/api/v1/projects/${this._client.project}/datamodelstorage/edges/delete`,
      {
        data,
        headers: CDF_ALPHA_VERSION_HEADERS,
        responseType: 'json',
        withCredentials: true,
      }
    );

    return;
  }
}

export const FDMQueryClientBuilder = {
  fromToken: (appId = 'FDMQueryClient') => {
    return new FDMQueryClient(
      new CogniteClientV6({
        appId,
        // @ts-ignore
        getToken: () => Promise.resolve(BEARER_TOKEN),
        project: PROJECT,
        baseUrl: `https://${CLUSTER}.cognitedata.com`,
      })
    );
  },
  fromClient: (client: CogniteClient) => {
    return new FDMQueryClient(client);
  },
};

/// <reference path='./codegen.d.ts'/>
export * from './codegen';
export * from './schema';

export class NodeRef {
  externalId: string;
  spaceId?: string;
  constructor(externalId: string, spaceId?: string) {
    this.externalId = externalId;
    this.spaceId = spaceId;
  }
  public buildValue = (defaultSpace: string) => {
    return [this.spaceId || defaultSpace, this.externalId];
  };
  public type = '__NODE_REF';
}

function isNodeRef(item: any): item is NodeRef {
  //magic happens here
  return (
    typeof item === 'object' &&
    'buildValue' in item &&
    'type' in item &&
    item.type === '__NODE_REF'
  );
}

const BEARER_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiJodHRwczovL2dyZWVuZmllbGQuY29nbml0ZWRhdGEuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMjY3Y2ZkZDgtYTIwNy00MzIwLTgwZjItYTQzNTJiMTUwNDhmLyIsImlhdCI6MTY2NTM4NTk2NiwibmJmIjoxNjY1Mzg1OTY2LCJleHAiOjE2NjUzOTA0OTIsImFjciI6IjEiLCJhaW8iOiJBWlFBYS84VEFBQUE2UzZZUDlVVEZKNUhLblZIaEdSUlc5MlhKQ1FTNDUrcWpDUlhVSlNtZC9DNXMzY0FCVHZkVW91SUM4RVE5dXdjTVhUbENkcXYvbjBXZGtUS3ZyUlE0NjZNNExsQzNTVldrT0I5M0tYU2RDLzh1enRITHBNdzU1TVlPVTJVNGQxNUcyNHNwczJieGlJa0tSOFZvVjA3RE5PWW4vR1UyQ0I1bmt0NzR1bmMybWN2Q2V6MkF4MUdNd3dOMGljVEpjM24iLCJhbXIiOlsicHdkIiwibWZhIl0sImFwcGlkIjoiNDc3MGMwZjEtN2JiNi00M2I1LThjMzctOTRmMmE5MzA2NzU3IiwiYXBwaWRhY3IiOiIwIiwiZW1haWwiOiJkYXZpZC5saXVAY29nbml0ZWRhdGEuY29tIiwiZmFtaWx5X25hbWUiOiJldmVyIiwiZ2l2ZW5fbmFtZSI6ImdyZWF0ZXN0IiwiZ3JvdXBzIjpbIjg5MGVlYjJmLTE1ODUtNGUyZi1hMjczLTNhNWI5YWU0OTJkMCIsImEyYmYyNTJhLWY3NDktNDRlNy1iODZlLWIzZTdlNDY1ODcwNyIsIjJhZGQ3NzkzLTNmNDQtNGYyNi1iY2E3LTEwYzFmMmRkNzMyNyIsIjhhMDE4NDJlLTY4NjEtNGYzOC1hNWFmLTI3NTljMjJjNmY0NiIsImIxODJlNWQ2LTY2MTUtNDE3Mi05MTA3LTRlYzYxODg3YTU4YiJdLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hOWFlNWI1NC0zNjAwLTQ5MTctYTlkYy0zMDIwNzIzMzYwYjMvIiwiaXBhZGRyIjoiODEuMTc1LjU3Ljc0IiwibmFtZSI6InlhIGJvaSIsIm9pZCI6IjJmNGRmYWM2LTNhYjgtNGE4MS1hMDc3LTk5ZjMyOWVjM2MxMyIsInJoIjoiMC5BVTRBMlAxOEpnZWlJRU9BOHFRMUt4VUVqX19CVEV5ZDlMbE1qZGdQSEF4cWdSTk9BR28uIiwic2NwIjoiREFUQS5DSEFOR0UgREFUQS5WSUVXIElERU5USVRZIHVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IlRZTHZDOFoyeUpsOU9QUGJaU2lZR0dWdTF0cnpSaUczTkpUZjFhQzhDTEEiLCJ0aWQiOiIyNjdjZmRkOC1hMjA3LTQzMjAtODBmMi1hNDM1MmIxNTA0OGYiLCJ1bmlxdWVfbmFtZSI6ImRhdmlkLmxpdUBjb2duaXRlZGF0YS5jb20iLCJ1dGkiOiJGeFNzNXVkNG5FVzlKMmJQQTY5M0FBIiwidmVyIjoiMS4wIn0.nbcNiDChPQnCdSdU3u9rRccHd7ugvi2Ls3ofw60HYARnVRwFsvACaVRDGe9ZMaPMwZX_kgi3VNGgpYjcHhMwoxHxVJWLVKvlxniwPfwsFk37c-Z5J2WH44F8HRP4PE3nMDla9K2CvLAwJVgiwzeq35GJ99A2HJuIxDd6mnhwFWR-gdAlVnPFAtEN4uC9tm9at2qhAwYlEy8jl60coRkJW0CSAc3SSqEQdgUe60WV7tbR3l3G898QxPdEniqjL4rFMCFQVbCy_QcXnh8jQf8vf4lZMEg9q_ud_ZmD6BAHsqBa5gCJVtp9AD_mbvz6LO5u1RZcbCcGjZ69QDA4OidrdA"
const CLUSTER="greenfield"
const PROJECT="platypus"
const TENANT="cogniteappdev.onmicrosoft.com"
const DM_NAME="moviedm"
const DM_VERSION="1"