import {
  PutCommand,
  QueryCommand,
  QueryCommandInput,
  DeleteCommand,
  DeleteCommandInput,
  GetCommand,
  GetCommandInput,
  BatchWriteCommand,
  DynamoDBDocumentClient,
} from '@aws-sdk/lib-dynamodb';
import { NativeAttributeValue } from '@aws-sdk/util-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

const dynamoDBClient = new DynamoDBClient({});

{% if tracing %}
import { captureAWSv3Client } from 'aws-xray-sdk';
const client = captureAWSv3Client(dynamoDBClient);
{% else %}
const client = dynamoDBClient;
{% endif %}

export const documentClient = DynamoDBDocumentClient.from(client);

const TableName = process.env.DYNAMODB_AUCTION_TABLE_NAME as string;

export const put = async <Model>(model: Model) => documentClient.send(new PutCommand({ Item: model, TableName }));

export const get = async (input: Partial<GetCommandInput>) =>
  documentClient.send(new GetCommand({ ...input, TableName } as GetCommandInput));

export const query = async (input: Partial<QueryCommandInput>) =>
  documentClient.send(new QueryCommand({ ...input, TableName }));

export const del = async (input: Partial<DeleteCommandInput>) =>
  documentClient.send(new DeleteCommand({ ...input, TableName } as DeleteCommandInput));

export type Page<Model> = {
  items: Model[];
  next?: string;
};

const encodeLastEvaluatedKey = (lastEvaluatedKey: { [key: string]: NativeAttributeValue }) => {
  const jsonString = JSON.stringify(lastEvaluatedKey);
  return Buffer.from(jsonString).toString('base64');
};

const decodeLastEvaluatedKey = (encodedLastEvaluatedKey: string) => {
  const jsonString = Buffer.from(encodedLastEvaluatedKey, 'base64').toString();
  return JSON.parse(jsonString);
};

export const queryPage = async <Model>(input: Partial<QueryCommandInput> & { next?: string }): Promise<Page<Model>> => {
  const ExclusiveStartKey = input.next ? decodeLastEvaluatedKey(input.next) : undefined;
  const queryInput: Partial<QueryCommandInput> = omit(['next'], input);
  const result = await query({ ...queryInput, ExclusiveStartKey });
  const items = result.Items ? (result.Items as Model[]) : [];
  if (result.LastEvaluatedKey) {
    const next = encodeLastEvaluatedKey(result.LastEvaluatedKey);
    return { items, next };
  }
  return { items };
};

export const batchDelete = async (keys: { id: string; sortKey: string }[]) => {
  const deleteRequests = keys.map((key) => ({
    DeleteRequest: {
      Key: key,
    },
  }));
  const input = {
    RequestItems: {
      [TableName]: deleteRequests,
    },
  };
  return documentClient.send(new BatchWriteCommand(input));
};

const omit = <T>(keys: string[], obj: object) => {
  const newObj: { [key: string]: unknown } = {};
  const entries = Object.entries(obj).filter(([k]) => !keys.includes(k));
  entries.forEach(([key, value]) => (newObj[key] = value));
  return newObj as unknown as T;
};
