using Amazon.DynamoDBv2;
using Amazon.Runtime;

//DynamoDB Table configuration 
public class DynamoDbTableConfig
{
	public string TableName { get; set; }
	public List<AttributeDefinition> AttributeDefinitions { get; set; }
	public List<KeySchemaElement> KeySchemaElements { get; set; }
	public List<Index> Indexes { get; set; }
	public List<AttributeDefinition> IndexAttributeDefinitions { get; set; }
}

public class AttributeDefinition
{
	public string AttributeName { get; set; }
	public string AttributeType { get; set; }
}

public class Index
{
	public string IndexName { get; set; }
	public List<KeySchemaElement> KeySchemaElements { get; set; }
	public Projection Projection { get; set; }
}

public class Projection
{
	public string ProjectionType { get; set; }
	public List<string> NonKeyAttributes { get; set; }
}

public class KeySchemaElement
{
	public string AttributeName { get; set; }
	public string KeyType { get; set; }
}

private IAmazonDynamoDB _dynamoDbClientSource;		//Use source account credentials
private IAmazonDynamoDB _dynamoDbClientTarget;		//Use target account credentials

//_dynamoDbClientTarget = new AmazonDynamoDBClient(AccessKeyId, SecretAccessKey);

public void CreateDynamoDbTable(DynamoDbTableConfig dynamoDbTableConfig)
{
	try
	{
		string tableName = dynamoDbTableConfig.TableName;
		TableDescription tableDescription = GetTableInformation(tableName);
		if (tableDescription != null)
			DeleteTable(tableName);

		CreateTable(dynamoDbTableConfig);
		EnableTTL(tableName);
	}
	catch (AmazonDynamoDBException ex)
	{
	}
}

private void EnableTTL(string tableName)
{
	_dynamoDbClientTarget.UpdateTimeToLive(new UpdateTimeToLiveRequest
	{
		TableName = tableName,
		TimeToLiveSpecification = new TimeToLiveSpecification
		{
			Enabled = true,
			AttributeName = "TTL"
		}
	});
}

private void CreateTable(DynamoDbTableConfig dynamoDbTableConfig)
{
	var request = new CreateTableRequest
	{
		AttributeDefinitions = dynamoDbTableConfig.AttributeDefinitions.Select(def => 
			new AttributeDefinition
			{
				AttributeName = def.AttributeName,
				AttributeType = def.AttributeType
			}).ToList(),
		KeySchema = dynamoDbTableConfig.KeySchemaElements.Select(e =>
			new KeySchemaElement()
			{ 
				AttributeName = e.AttributeName, 
				KeyType = e.KeyType 
			}).ToList(),
		BillingMode = BillingMode.PAY_PER_REQUEST,
		TableName = dynamoDbTableConfig.TableName,
		GlobalSecondaryIndexes = GetGSIsFromConfig(dynamoDbTableConfig.Indexes)                
	};

	var response = _dynamoDbClientTarget.CreateTable(request);
	
	var tableDescription = response.TableDescription;
	string status = tableDescription.TableStatus;

	//Log table creation status
	
	WaitUntilTableReady(dynamoDbTableConfig.TableName);
}

private List<GlobalSecondaryIndex> GetGSIsFromConfig(List<Index> indexes)
{
	List<GlobalSecondaryIndex> gsiIndexes = new List<GlobalSecondaryIndex>();
	foreach (var index in indexes)
	{
		var gsi = new GlobalSecondaryIndex()
		{
			IndexName = index.IndexName,
			KeySchema = index.KeySchemaElements.Select(e =>
				new KeySchemaElement
				{
					AttributeName = e.AttributeName,
					KeyType = e.KeyType
				}).ToList(),
			Projection = new Projection
			{
				ProjectionType = index.Projection.ProjectionType,
				NonKeyAttributes = index.Projection.NonKeyAttributes
			}
		};

		gsiIndexes.Add(gsi);
	}

	return gsiIndexes;
}

private TableDescription GetTableInformation(string tableName)
{
	var request = new DescribeTableRequest { TableName = tableName };
	try
	{
		var response = _dynamoDbClientTarget.DescribeTable(request);
		return response?.Table;
	}
	catch (Exception ex)
	{
	}

	return null;
}

private void DeleteTable(string tableName)
{
	var request = new DeleteTableRequest { TableName = tableName };
	var response = _dynamoDbClientTarget.DeleteTable(request);
	System.Threading.Thread.Sleep(5000);
}

private void WaitUntilTableReady(string tableName)
{
	string status = null;
	do
	{
		System.Threading.Thread.Sleep(5000);
		try
		{
			var request = new DescribeTableRequest { TableName = tableName };
			var response = _dynamoDbClientTarget.DescribeTable(request);
			status = response.Table.TableStatus;
		}
		catch (ResourceNotFoundException)
		{
			// DescribeTable is eventually consistent. So you might
			// get resource not found. So we handle the potential exception.
		}
	} while (status != "ACTIVE");
}
		
//Copy all items from sourceTable (Account-A) to targetTable (Account-B)
//Return count of consumed RCUs, WCUs and UnprocessedItems

public Tuple<double, double, double> SyncTables(string sourceTable, string targetTable)
{​​​
	double readCapacityUnits = 0;
	double writeCapacityUnits = 0;
	double unprocessedItems = 0;

	Dictionary<string, AttributeValue> lastEvaluatedKey = null;
	
	do
	{​​​
		var req = new ScanRequest
		{​​​
			TableName = sourceTable,
			ExclusiveStartKey = lastEvaluatedKey,
			ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL
		}​​​;
		
		var resp = _dynamoDbClientSource.Scan(req);
		
		if (resp.Items?.Count > 0)
		{​​​
			var units = PutItemsToTargetTable(resp.Items, targetTable, out unprocessedItems);
			writeCapacityUnits += units;
		}​​​
		
		readCapacityUnits += resp.ConsumedCapacity.CapacityUnits;
		writeCapacityUnits += resp.ConsumedCapacity.WriteCapacityUnits;
		lastEvaluatedKey = resp.LastEvaluatedKey;
	}​​​ while (lastEvaluatedKey != null && lastEvaluatedKey.Count > 0);
	 
	return new Tuple<double, double, double>(readCapacityUnits, writeCapacityUnits, unprocessedItems);
}​​​

private double PutItemsToTargetTable(List<Dictionary<string, AttributeValue>> itemsToWrite, string targetTableName, out double unProcessedCount)
{​​​
	double capacityConsumed = 0;
	int retryCount = 0;
	int unprocessedItemsCount = 0;
	
	unProcessedCount = 0;
	var unprocessedItems = new List<Dictionary<string, AttributeValue>>();
	var items = itemsToWrite;
	
	try
	{​​​
		//Retry 3 times - to process/write UnprocessedItems
		while (items.Count > 0 && retryCount < 3)
		{​​​
			int count = 0;
			unprocessedItemsCount = 0;
			
			var wrList = new List<WriteRequest>();
			var batchWriteItemRequest = new BatchWriteItemRequest();
			
			foreach (var item in items)
			{​​​
				wrList.Add(new WriteRequest() {​​​ PutRequest = new PutRequest(item) }​​​);
				
				//BatchWriteItem can write up to 25 items (or up to 16 MB) at a time
				//Accumulate items in counts of 20 and then write to targetTable using BatchWriteItem()
				
				count++;
				if (count % 20 == 0 || count == items.Count)
				{​​​
					var requestItems = new Dictionary<string, List<WriteRequest>>();
					requestItems.Add(targetTableName, wrList);
					
					var response = _dynamoDbClientTarget.BatchWriteItem(new BatchWriteItemRequest(requestItems){​​​ ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL }​​​);
					
					if (response?.ConsumedCapacity != null)
						response.ConsumedCapacity.ForEach(c => capacityConsumed += c.CapacityUnits);
					
					if (response.UnprocessedItems?.Count > 0)
					{​​​
						unprocessedItemsCount += response.UnprocessedItems[targetTableName].Count;
						response.UnprocessedItems[targetTableName].ForEach(u =>
						{​​​
							unprocessedItems.Add(u.PutRequest.Item);
						}​​​);
					}​​​
					
					if (response.HttpStatusCode != System.Net.HttpStatusCode.OK)
					{	
						//Handle error status 
						break;
					}
					
					wrList = new List<WriteRequest>();
				}​​​
			}​​​
			items = unprocessedItems;
			unprocessedItems = new List<Dictionary<string, AttributeValue>>();
			retryCount++;
		}​​​
		unProcessedCount = unprocessedItemsCount;
	}​​​
	catch(Exception ex)
	{​​​
		Console.WriteLine("Exception in PutItemsToTargetTable: " + ex.Message);
		return 0;
	}​​​
	
	return capacityConsumed;
}​​​



