import BigNumber from 'bignumber.js'
import { IMessDbView } from '../../'
import {
	CONTRACT_INTERFACES as CI,
	CONTRACT_INTERFACES,
	IChain,
	IPortfolioView,
	IPortfolioViewEntry,
	IStrategyChain,
	IdeaCreatedEvent,
	MyIdeaKeys,
	copyObj,
	LogModule as log,
	uniqueKeyJoin,
	uniqueKeySplit,
} from '../../../../../'

interface Creator {
	creatorWallet: string
	strategies: Set<string>
	strategiesByOldest: string[]
	createdIdeasIndexesByOldest: string[]
}

interface IRebalanceMap {
	index: number
	reference: string
	ideaIndexes: string[]
}

interface MyContractDb {
	contractUniqueKey: string // unique,
	chain: IChain
	contractAddress: string
	creators: Map<string, Creator>
	creatorsByOldest: string[]
	userAccessStrategies: Map<string, Set<string>>
	userSubscribedToStrategies: Map<string, Set<string>>
	userAccessIdeaNfts: Map<string, Set<number>>
	userAccessIdeaIndexesByOldest: Map<string, string[]>
	userAccessIdeaKeysByStrategy: Map<string, Map<string, Set<number>>>
	publicStrategies: Set<string>
	publicStrategiesByOldest: string[]
	publicStrategiesFilled: Map<string, CONTRACT_INTERFACES.ITradeIdeaStrategy>
	ideaIndexByOldest: string[]
	publicIdeaIndexByOldest: string[]
	ideaIndexByStrategyByOldest: Map<string, string[]>
	ideaIndexToKeys: Map<string, MyIdeaKeys>
	ideaIndexToData: Map<string, CONTRACT_INTERFACES.ITradeIdea>
	allocationStrategyPortfolios: Map<string, IPortfolioView | 'ENCRYPTED'>
	allocationRebalances: Map<string, IRebalanceMap[]>
}

export interface MyDbViewState {
	contractDbs: Map<string, MyContractDb>
	contractsByOldest: string[]
}

const state: MyDbViewState = {
	contractDbs: new Map<string, MyContractDb>(),
	contractsByOldest: [] as string[],
}

const getContractsInMemoryState = (): MyDbViewState => {
	return state
}

const setContractsInMemoryState = (_state: MyDbViewState) => {
	state.contractDbs = _state.contractDbs
	state.contractsByOldest = _state.contractsByOldest
}

const reduce = async (
	contractUniqueKey: string,
	newIdeaEvent: IdeaCreatedEvent,
	strategyChain: IStrategyChain,
	strategyCache: CONTRACT_INTERFACES.IStrategyState,
): Promise<void> => {
	// console.log('++++++++++++++++++++++++++++++++++++++')
	// console.log('++++++++++++++++++++++++++++++++++++++')
	// console.log('++++++++++++++++++++++++++++++++++++++')

	log.dev('+reducing+')
	log.dev('contractUniqueKey')
	log.dev(contractUniqueKey)
	// log.dev('newIdeaEvent')
	// log.dev(newIdeaEvent)
	const decomposed = uniqueKeySplit(contractUniqueKey)
	const chain = decomposed[0] as IChain
	const contractAddress = decomposed[1]
	log.dev('contractAddress')
	log.dev(contractAddress)
	log.dev('chain')
	log.dev(chain)

	const nftId = newIdeaEvent.nftId // unique within contract
	const strategyUniqueKey = uniqueKeyJoin([
		chain,
		contractAddress,
		newIdeaEvent.creatorAddress,
		newIdeaEvent.strategyKey,
	])
	const ideaIndex = uniqueKeyJoin([chain, contractAddress, '' + nftId])
	const myIdeaKeys: MyIdeaKeys = {
		ideaIndex,
		strategyUniqueKey,
		chain,
		nftId,
		contractAddress,
		strategyKey: newIdeaEvent.strategyKey,
		stageKey: newIdeaEvent.stageKey,
		ideaKey: newIdeaEvent.ideaKey,
	}
	let myContractDb = state.contractDbs.get(contractUniqueKey)
	if (myContractDb === undefined) {
		myContractDb = {
			contractUniqueKey,
			chain,
			contractAddress,
			creators: new Map<string, Creator>(),
			creatorsByOldest: [] as string[],
			userAccessStrategies: new Map<string, Set<string>>(),
			userSubscribedToStrategies: new Map<string, Set<string>>(),
			userAccessIdeaNfts: new Map<string, Set<number>>(),
			userAccessIdeaIndexesByOldest: new Map<string, string[]>(),
			userAccessIdeaKeysByStrategy: new Map<
				string,
				Map<string, Set<number>>
			>(),
			publicStrategies: new Set<string>(),
			publicStrategiesByOldest: [] as string[],
			publicStrategiesFilled: new Map<
				string,
				CONTRACT_INTERFACES.ITradeIdeaStrategy
			>(),
			ideaIndexByOldest: [],
			publicIdeaIndexByOldest: [],
			ideaIndexByStrategyByOldest: new Map<string, string[]>(),
			ideaIndexToKeys: new Map<string, MyIdeaKeys>(),
			ideaIndexToData: new Map<string, CONTRACT_INTERFACES.ITradeIdea>(),
			allocationStrategyPortfolios: new Map<
				string,
				IPortfolioView | 'ENCRYPTED'
			>(),
			allocationRebalances: new Map<string, IRebalanceMap[]>(),
		}
		log.dev('setting up state.contractDbs')
		log.dev('contractUniqueKey')
		log.dev(contractUniqueKey)
		log.dev('myContractDb')
		log.dev(myContractDb)
		state.contractDbs.set(contractUniqueKey, myContractDb)
		state.contractsByOldest.push(contractUniqueKey)
		log.dev('state.contractsByOldest')
		log.dev(state.contractsByOldest)
	}
	myContractDb = state.contractDbs.get(contractUniqueKey)!
	myContractDb.publicStrategiesFilled.set(
		strategyUniqueKey,
		strategyCache.strategy,
	)
	let creator = myContractDb.creators.get(newIdeaEvent.creatorAddress)
	if (!creator) {
		myContractDb.creatorsByOldest.push(newIdeaEvent.creatorAddress)
		myContractDb.creators.set(newIdeaEvent.creatorAddress, {
			creatorWallet: newIdeaEvent.creatorAddress,
			strategies: new Set<string>(),
			strategiesByOldest: [] as string[],
			createdIdeasIndexesByOldest: [] as string[],
		})
	}
	creator = myContractDb.creators.get(newIdeaEvent.creatorAddress)!
	if (!creator.strategies.has(strategyUniqueKey)) {
		creator.strategies.add(strategyUniqueKey)
		creator.strategiesByOldest.push(strategyUniqueKey)
	}
	creator.createdIdeasIndexesByOldest.push(ideaIndex)

	const stg = myContractDb.ideaIndexByStrategyByOldest.get(strategyUniqueKey)
	if (!stg) {
		myContractDb.ideaIndexByStrategyByOldest.set(strategyUniqueKey, [
			ideaIndex,
		])
	} else {
		stg.push(ideaIndex)
	}
	if (!myContractDb.ideaIndexToKeys.has(ideaIndex)) {
		myContractDb.ideaIndexToKeys.set(ideaIndex, myIdeaKeys)
		myContractDb.ideaIndexByOldest.push(ideaIndex)
	}

	const idea = strategyChain.ideas.find(
		(idea) => idea.ideaKey === newIdeaEvent.ideaKey,
	)
	const ideaStage = idea!.stages.find(
		(ideaStage) => ideaStage.ideaStageKey === newIdeaEvent.stageKey,
	)
	const openSeaIdea = ideaStage!.ideaStage
	myContractDb.ideaIndexToData.set(ideaIndex, openSeaIdea)

	const accessors = openSeaIdea.access?.wallets
	const addSubscriber = (subscriber: string) => {
		if (subscriber !== newIdeaEvent.creatorAddress) {
			let userSubscribedToStrategies =
				myContractDb!.userSubscribedToStrategies.get(subscriber)
			if (userSubscribedToStrategies === undefined) {
				myContractDb!.userSubscribedToStrategies.set(
					subscriber,
					new Set<string>(),
				)
				userSubscribedToStrategies =
					myContractDb!.userSubscribedToStrategies.get(subscriber)!
			}
			userSubscribedToStrategies.add(strategyUniqueKey)
		}
	}
	const addAccessor = (accessor: string) => {
		let userAccessStrategies =
			myContractDb!.userAccessStrategies.get(accessor)
		if (userAccessStrategies === undefined) {
			myContractDb!.userAccessStrategies.set(accessor, new Set<string>())
			userAccessStrategies =
				myContractDb!.userAccessStrategies.get(accessor)!
		}
		userAccessStrategies.add(strategyUniqueKey)

		if (!myContractDb!.userAccessIdeaNfts.has(accessor)) {
			myContractDb!.userAccessIdeaNfts.set(accessor, new Set<number>())
			myContractDb!.userAccessIdeaIndexesByOldest.set(accessor, [])
			myContractDb!.userAccessIdeaKeysByStrategy.set(
				accessor,
				new Map<string, Set<number>>(),
			)
		}
		const userAccessIdeaNfts =
			myContractDb!.userAccessIdeaNfts.get(accessor)!
		if (!userAccessIdeaNfts.has(nftId)) {
			userAccessIdeaNfts.add(nftId)
			myContractDb!.userAccessIdeaIndexesByOldest
				.get(accessor)!
				.push(ideaIndex)
		}
		const userAccessIdeaKeysByStrategy =
			myContractDb!.userAccessIdeaKeysByStrategy.get(accessor)!
		if (!userAccessIdeaKeysByStrategy.has(strategyUniqueKey)) {
			userAccessIdeaKeysByStrategy.set(
				strategyUniqueKey,
				new Set<number>(),
			)
		}
		// console.log('well done')

		const stgSet = userAccessIdeaKeysByStrategy.get(strategyUniqueKey)!
		stgSet.add(newIdeaEvent.ideaKey)
	}
	addAccessor(newIdeaEvent.creatorAddress)
	if (accessors !== undefined) {
		// console.log('test')
		for (const accessor of accessors) {
			addAccessor(accessor)
			addSubscriber(accessor)
		}
		// console.log(openSeaIdea.idea)
		// console.log('openSeaIea.idea')
		// console.log('fasdjfhsdujkhfjklashfljkahsdjkhlasdjkfajklshflj')
		// console.log('fasdjfhsdujkhfjklashfljkahsdjkhlasdjkfajklshflj')
		// console.log('fasdjfhsdujkhfjklashfljkahsdjkhlasdjkfajklshflj')
		if (typeof openSeaIdea.idea !== 'string') {
			if (openSeaIdea.idea.kind === 'close') {
				// console.log(openSeaIdea.idea)
				// console.log('openSeaIea.idea')
				if (!myContractDb.publicStrategies.has(strategyUniqueKey)) {
					myContractDb.publicStrategies.add(strategyUniqueKey)
					myContractDb.publicStrategiesByOldest.push(
						strategyUniqueKey,
					)
				}
				myContractDb.publicIdeaIndexByOldest.push(ideaIndex)
			}
		}
	} else {
		// console.log('the case')
		if (!myContractDb.publicStrategies.has(strategyUniqueKey)) {
			myContractDb.publicStrategies.add(strategyUniqueKey)
			myContractDb.publicStrategiesByOldest.push(strategyUniqueKey)
		}
		myContractDb.publicIdeaIndexByOldest.push(ideaIndex)
	}
	// console.log('===================================')
	// console.log('===================================')
	if (openSeaIdea.strategy.ordersType === 'ALLOCATION') {
		// process allocation
		if (!myContractDb.allocationStrategyPortfolios.has(strategyUniqueKey)) {
			myContractDb.allocationStrategyPortfolios.set(strategyUniqueKey, {
				strategyKey: strategyUniqueKey,
				totalEntries: 0,
				totalAllocation: 0,
				entries: [],
			})
		}
		if (
			myContractDb.allocationStrategyPortfolios.get(strategyUniqueKey) !==
			'ENCRYPTED'
		) {
			const hasEncryption = openSeaIdea.access !== undefined
			if (hasEncryption) {
				myContractDb.allocationStrategyPortfolios.set(
					strategyUniqueKey,
					'ENCRYPTED',
				)
			} else {
				const innerIdea = openSeaIdea.idea as CI.ITradeIdeaIdea

				const portfolio = myContractDb.allocationStrategyPortfolios.get(
					strategyUniqueKey,
				)! as IPortfolioView
				// console.log('+++++++++++++++++++++++++++++++++++')
				// console.log('++++++++++++ CALC PORTFOLIO +++++++')
				// console.log('+++++++++++++++++++++++++++++++++++')
				// console.log('PRIOR STEP: ')
				// console.log('portfolio.totalEntries')
				// console.log(portfolio.totalEntries)
				// console.log('portfolio.totalAllocation')
				// console.log(portfolio.totalAllocation)
				// console.log('portfolio.entries')
				// console.log(portfolio.entries)
				// console.log('STEP:')
				// console.log('innerIdea')
				// console.log(innerIdea)

				if (innerIdea.kind === 'open') {
					portfolio.totalEntries++
					const portEntry: IPortfolioViewEntry = {
						reference: openSeaIdea.content.reference,
						pricing: openSeaIdea.pricing,
						price: innerIdea.priceInfo!.price!,
						notes: innerIdea.notes.commentary,
						title: innerIdea.title,
						asset: innerIdea.asset,
						direction: innerIdea.trade?.direction,
						allocation: innerIdea.trade!.allocation!,
					}
					portfolio.entries.push(portEntry)
					portfolio.totalAllocation = +BigNumber(
						portfolio.totalAllocation,
					)
						.plus(portEntry.allocation)
						.toFixed(2)
				} else if (innerIdea.kind === 'adjust') {
					const portEntry = portfolio.entries.find(
						(entry) =>
							entry.asset.ticker === innerIdea.asset.ticker,
					)!
					portfolio.totalAllocation = +BigNumber(
						portfolio.totalAllocation,
					)
						.minus(portEntry.allocation)
						.toFixed(2)
					portEntry.reference = openSeaIdea.content.reference
					portEntry.price = innerIdea.priceInfo!.price!
					portEntry.notes = innerIdea.notes.commentary
					portEntry.title = innerIdea.title || portEntry.title
					portEntry.asset = innerIdea.asset
					portEntry.direction =
						innerIdea.trade?.direction || portEntry.direction
					if (innerIdea.adjustment!.kind === 'increase') {
						portEntry.allocation = +BigNumber(portEntry.allocation)
							.plus(
								BigNumber(portEntry.allocation).times(
									BigNumber(
										innerIdea.adjustment!.percentage,
									).dividedBy(100),
								),
							)
							.toFixed(2)
					} else {
						portEntry.allocation = +BigNumber(portEntry.allocation)
							.minus(
								BigNumber(portEntry.allocation).times(
									BigNumber(
										innerIdea.adjustment!.percentage,
									).dividedBy(100),
								),
							)
							.toFixed(2)
					}
					portfolio.totalAllocation = +BigNumber(
						portfolio.totalAllocation,
					)
						.plus(portEntry.allocation)
						.toFixed(2)
				} else if (innerIdea.kind === 'close') {
					const portEntryIndex = portfolio.entries.findIndex(
						(entry) =>
							entry.asset.ticker === innerIdea.asset.ticker,
					)
					if (portEntryIndex !== -1) {
						portfolio.totalAllocation = +BigNumber(
							portfolio.totalAllocation,
						)
							.minus(portfolio.entries[portEntryIndex].allocation)
							.toFixed(2)
						portfolio.entries.splice(portEntryIndex, 1)
						portfolio.totalEntries--
					}
				}

				// console.log('AFTER STEP:')
				// console.log('portfolio.totalEntries')
				// console.log(portfolio.totalEntries)
				// console.log('portfolio.totalAllocation')
				// console.log(portfolio.totalAllocation)
				// console.log('portfolio.entries')
				// console.log(portfolio.entries)
				// console.log('----------------------------')
				// console.log('----------------------------')
				// console.log('----------------------------')
				// await rest(3000)
			}
		}
		// console.log('_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_')
		// console.log('_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_')
		// console.log('_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_')
		// console.log('_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_')
		// console.log('openSeaIdea.idea as CI.ITradeIdeaIdea')
		// console.log(openSeaIdea.idea as CI.ITradeIdeaIdea)
		// console.log('openSeaIdea.content.reference')
		// console.log(openSeaIdea.content.reference)

		// process rebalances
		const allocationRebalances =
			myContractDb.allocationRebalances.get(strategyUniqueKey)
		if (allocationRebalances === undefined) {
			const allocationRebalances = []
			allocationRebalances.push({
				index: 0,
				reference: openSeaIdea.content.reference,
				ideaIndexes: [ideaIndex],
			})
			myContractDb.allocationRebalances.set(
				strategyUniqueKey,
				allocationRebalances,
			)
		} else {
			const lastOne =
				allocationRebalances[allocationRebalances.length - 1]
			// console.log('lastOne')
			// console.log(lastOne)
			if (openSeaIdea.content.reference === lastOne.reference) {
				lastOne.ideaIndexes.push(ideaIndex)
			} else {
				allocationRebalances.push({
					index: allocationRebalances.length,
					reference: openSeaIdea.content.reference,
					ideaIndexes: [ideaIndex],
				})
			}
		}

		// await rest(3000)
	}
	log.dev('-reduced-')
}

const listAllContracts = async (): Promise<string[]> => {
	// console.log('I HAVE BEEN')
	// console.log('I HAVE BEEN')
	// console.log('state.contractsByOldest')
	// console.log(state.contractsByOldest)
	return [...state.contractsByOldest]
}

const listAllStrategiesByContractAndUser = async (
	uniqueContractKey: string,
	userWallet: string,
): Promise<string[]> => {
	// console.log('uniqueContractKey')
	// console.log(uniqueContractKey)
	// console.log('userWallet')
	// console.log(userWallet)
	// throw new Error('check')

	const allStrategies = [
		...(await listContractCreatorStrategies(uniqueContractKey, userWallet)),
		...(await listContractUserWithAccessibleIdeasStrategies(
			uniqueContractKey,
			userWallet,
		)),
		...(await listContractWithPublicIdeasStrategies(uniqueContractKey)),
	]
	// filterOfDuplications without losing order
	const seen = new Set()
	const filtered = allStrategies.filter((item) => {
		if (!seen.has(item)) {
			seen.add(item)
			return true
		}
		return false
	})
	return [...filtered]
}

const getStrategyLastIdea = async (
	uniqueStrategyReference: string,
): Promise<CONTRACT_INTERFACES.ITradeIdea> => {
	const decomposed = uniqueKeySplit(uniqueStrategyReference)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(
			`Contract not found for strategy: ${uniqueStrategyReference}`,
		)
	}
	const stg = myContractDb.ideaIndexByStrategyByOldest.get(
		uniqueStrategyReference,
	)
	if (!stg) {
		throw new Error(`Strategy not found: ${uniqueStrategyReference}`)
	}
	const lastIdeaIndex = stg[stg.length - 1]
	const ideaData = myContractDb.ideaIndexToData.get(lastIdeaIndex)
	if (!ideaData) {
		throw new Error(`Idea not found: ${lastIdeaIndex}`)
	}
	return copyObj(ideaData)
}

const getStrategy = async (
	uniqueStrategyReference: string,
): Promise<CONTRACT_INTERFACES.ITradeIdeaStrategy> => {
	const decomposed = uniqueKeySplit(uniqueStrategyReference)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(
			`Contract not found for strategy: ${uniqueStrategyReference}`,
		)
	}
	const stg = myContractDb.publicStrategiesFilled.get(uniqueStrategyReference)
	if (!stg) {
		throw new Error(`Strategy not found: ${uniqueStrategyReference}`)
	}
	return copyObj(stg!)
}

const getStrategyCreator = async (
	uniqueStrategyReference: string,
): Promise<CONTRACT_INTERFACES.ITradeIdeaCreator> => {
	const lastIdea = await getStrategyLastIdea(uniqueStrategyReference)
	return lastIdea.creator
}

const getCreator = async (
	uniqueContractKey: string,
	creatorWallet: string,
): Promise<CONTRACT_INTERFACES.ITradeIdeaCreator> => {
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(`Contract not found for creator: ${uniqueContractKey}`)
	}
	// console.log('creatorWallet')
	// console.log(creatorWallet)
	// console.log('myContractDb.creators')
	// console.log(myContractDb.creators)
	const creator = myContractDb.creators.get(creatorWallet)
	if (!creator) {
		throw new Error(`Creator not found: ${creatorWallet}`)
	}
	const lastIdea =
		creator.createdIdeasIndexesByOldest[
			creator.createdIdeasIndexesByOldest.length - 1
		]
	const ideaData = myContractDb.ideaIndexToData.get(lastIdea)
	if (!ideaData) {
		throw new Error(`Idea not found: ${lastIdea}`)
	}
	return copyObj(ideaData.creator)
}

const getStrategyWithCreator = async (
	uniqueStrategyReference: string,
): Promise<{
	strategy: CONTRACT_INTERFACES.ITradeIdeaStrategy
	creator: CONTRACT_INTERFACES.ITradeIdeaCreator
}> => {
	const lastIdea = await getStrategyLastIdea(uniqueStrategyReference)
	return copyObj({
		strategy: await getStrategy(uniqueStrategyReference),
		creator: lastIdea.creator,
	})
}

const listContractWithPublicIdeasStrategies = async (
	uniqueContractKey: string,
): Promise<string[]> => {
	// console.log('uniqueContractKey')
	// console.log(uniqueContractKey)
	// console.log('state.contractDbs')
	// console.log(state.contractDbs)
	// console.log('dfhjasdfjkdhsflkasdhfjkhasdjk')
	// console.log('dfhjasdfjkdhsflkasdhfjkhasdjk')
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	// console.log('myContractDb')
	// console.log(myContractDb)
	if (myContractDb === undefined) {
		return []
	}
	// console.log('myContractDb.publicStrategiesByOldest')
	// console.log(myContractDb.publicStrategiesByOldest)
	// throw new Error('fads')
	return [...myContractDb.publicStrategiesByOldest]
}

const listAllIdeasIndexesByStrategy = async (
	uniqueStrategyKey: string,
): Promise<string[]> => {
	const decomposed = uniqueKeySplit(uniqueStrategyKey)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(`Contract not found strategy: ${uniqueStrategyKey}`)
	}
	const stg = myContractDb.ideaIndexByStrategyByOldest.get(uniqueStrategyKey)
	if (stg === undefined) {
		return []
	}
	return copyObj(stg)
}

const listAllIdeasKeysByStrategy = async (
	uniqueStrategyKey: string,
): Promise<MyIdeaKeys[]> => {
	const decomposed = uniqueKeySplit(uniqueStrategyKey)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(`Contract not found strategy: ${uniqueStrategyKey}`)
	}
	const stg = myContractDb.ideaIndexByStrategyByOldest.get(uniqueStrategyKey)
	if (stg === undefined) {
		return []
	}
	return [...stg.map((each) => myContractDb.ideaIndexToKeys.get(each)!)]
}

const listAllIdeasIndexesByContract = async (
	uniqueContractKey: string,
): Promise<string[]> => {
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		return []
	}
	return [...myContractDb.ideaIndexByOldest]
}

const listAllPublicIdeasIndexesByContract = async (
	uniqueContractKey: string,
): Promise<string[]> => {
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		return []
	}
	return [...myContractDb.publicIdeaIndexByOldest]
}

const listCreatedByIdeasIndexesByContract = async (
	uniqueContractKey: string,
	creatorWallet: string,
): Promise<string[]> => {
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		return []
	}
	const creator = myContractDb.creators.get(creatorWallet)
	if (creator === undefined) {
		return []
	}
	return [...creator.createdIdeasIndexesByOldest]
}

const listOwnedByIdeasIndexesByContract = async (
	uniqueContractKey: string,
	ownerWallet: string,
): Promise<string[]> => {
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		return []
	}
	const ownerNfts =
		myContractDb.userAccessIdeaIndexesByOldest.get(ownerWallet)
	if (ownerNfts === undefined) {
		return []
	}
	return [...ownerNfts]
}

const listOwnedByIdeasKeysByContract = async (
	uniqueContractKey: string,
	ownerWallet: string,
): Promise<MyIdeaKeys[]> => {
	const indexes = await listOwnedByIdeasIndexesByContract(
		uniqueContractKey,
		ownerWallet,
	)
	const myContractDb = state.contractDbs.get(uniqueContractKey)!

	return [
		...indexes.map((each) => {
			return myContractDb.ideaIndexToKeys.get(each)!
		}),
	]
}

const getIdeaKeys = async (ideaIndex: string): Promise<MyIdeaKeys> => {
	const decomposed = uniqueKeySplit(ideaIndex)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(`Contract not found for idea: ${ideaIndex}`)
	}
	const myIdeaKeys = myContractDb.ideaIndexToKeys.get(ideaIndex)
	if (myIdeaKeys === undefined) {
		throw new Error(`Idea not found: ${ideaIndex}`)
	}
	return copyObj(myIdeaKeys)
}

const getIdeaData = async (
	ideaIndex: string,
): Promise<CONTRACT_INTERFACES.ITradeIdea> => {
	const decomposed = uniqueKeySplit(ideaIndex)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(`Contract not found for idea: ${ideaIndex}`)
	}
	const ideaData = myContractDb.ideaIndexToData.get(ideaIndex)
	if (ideaData === undefined) {
		throw new Error(`Idea not found: ${ideaIndex}`)
	}
	return copyObj(ideaData)
}

const listContractCreatorStrategies = async (
	uniqueContractKey: string,
	creatorWallet: string,
): Promise<string[]> => {
	const contractDb = state.contractDbs.get(uniqueContractKey)
	if (!contractDb)
		throw new Error(
			'No contractDb found for uniqueContractKey: ' + uniqueContractKey,
		)
	const creator = contractDb.creators.get(creatorWallet)
	if (!creator)
		throw new Error('No creator found for creatorWallet: ' + creatorWallet)
	// console.log('creator.strategiesByOldest')
	// console.log(creator.strategiesByOldest)
	// throw new Error('check')
	return [...creator.strategiesByOldest]
}

const listContractUserWithAccessibleIdeasStrategies = async (
	uniqueContractKey: string,
	userWallet: string,
): Promise<string[]> => {
	const contractDb = state.contractDbs.get(uniqueContractKey)
	if (!contractDb)
		throw new Error(
			'No contractDb found for uniqueContractKey: ' + uniqueContractKey,
		)
	// console.log(userWallet)
	const userAccessStrategies = contractDb.userAccessStrategies.get(userWallet)
	// console.log('userAccessStrategies')
	// console.log(userAccessStrategies)
	if (userAccessStrategies === undefined) return []
	return Array.from(userAccessStrategies)
}

const listContractUserSubscribedToIdeasStrategies = async (
	uniqueContractKey: string,
	userWallet: string,
): Promise<string[]> => {
	const contractDb = state.contractDbs.get(uniqueContractKey)
	if (!contractDb)
		throw new Error(
			'No contractDb found for uniqueContractKey: ' + uniqueContractKey,
		)
	const userAccessStrategies =
		contractDb.userSubscribedToStrategies.get(userWallet)
	if (userAccessStrategies === undefined) return []
	return Array.from(userAccessStrategies)
}

const listContractUserAccessibleIdeas = async (
	uniqueContractKey: string,
	userWallet: string,
): Promise<number[]> => {
	const contractDb = state.contractDbs.get(uniqueContractKey)
	if (!contractDb)
		throw new Error(
			'No contractDb found for uniqueContractKey: ' + uniqueContractKey,
		)
	const userAccessIdeaNfts = contractDb.userAccessIdeaNfts.get(userWallet)
	if (userAccessIdeaNfts === undefined) return [] as number[]
	return Array.from(userAccessIdeaNfts)
}

const listContractUserAccessibleIdeasByStrategy = async (
	uniqueContractKey: string,
	userWallet: string,
	uniqueStrategyKey: string,
): Promise<number[]> => {
	const contractDb = state.contractDbs.get(uniqueContractKey)
	if (!contractDb)
		throw new Error(
			'No contractDb found for uniqueContractKey: ' + uniqueContractKey,
		)
	const userAccessIdeaKeysByStrategy =
		contractDb.userAccessIdeaKeysByStrategy.get(userWallet)
	if (userAccessIdeaKeysByStrategy === undefined) return [] as number[]
	const userAccessIdeaKeys =
		userAccessIdeaKeysByStrategy.get(uniqueStrategyKey)
	if (userAccessIdeaKeys === undefined) return [] as number[]
	return Array.from(userAccessIdeaKeys)
}

const getStrategyPortfolio = async (
	uniqueStrategyReference: string,
): Promise<IPortfolioView | 'ENCRYPTED'> => {
	const decomposed = uniqueKeySplit(uniqueStrategyReference)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(
			`Contract not found for strategy: ${uniqueStrategyReference}`,
		)
	}
	const portfolio = myContractDb.allocationStrategyPortfolios.get(
		uniqueStrategyReference,
	)
	if (portfolio === undefined) {
		throw new Error(`Strategy not found: ${uniqueStrategyReference}`)
	}
	return copyObj(portfolio)
}

const getStrategyRebalances = async (
	uniqueStrategyReference: string,
): Promise<IRebalanceMap[]> => {
	const decomposed = uniqueKeySplit(uniqueStrategyReference)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(
			`Contract not found for strategy: ${uniqueStrategyReference}`,
		)
	}
	const rebalances = myContractDb.allocationRebalances.get(
		uniqueStrategyReference,
	)
	if (rebalances === undefined) {
		throw new Error(`Strategy not found: ${uniqueStrategyReference}`)
	}
	return copyObj(rebalances)
}

const getStrategyRebalance = async (
	uniqueStrategyReference: string,
	rebalanceReference: string,
): Promise<IRebalanceMap> => {
	const decomposed = uniqueKeySplit(uniqueStrategyReference)
	const uniqueContractKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const myContractDb = state.contractDbs.get(uniqueContractKey)
	if (myContractDb === undefined) {
		throw new Error(
			`Contract not found for strategy: ${uniqueStrategyReference}`,
		)
	}
	const rebalances = myContractDb.allocationRebalances.get(
		uniqueStrategyReference,
	)
	if (rebalances === undefined) {
		throw new Error(`Strategy not found: ${uniqueStrategyReference}`)
	}
	const rebalance = rebalances.find(
		(rebalance) => rebalance.reference === rebalanceReference,
	)
	if (rebalance === undefined) {
		throw new Error(`Rebalance not found: ${rebalanceReference}`)
	}
	return copyObj(rebalance)
}

const getStrategyRebalancesInPeriod = async (
	uniqueStrategyReference: string,
	startDate: number,
	endDate: number,
): Promise<IRebalanceMap[]> => {
	const rebalances = await getStrategyRebalances(uniqueStrategyReference)
	// First we filter out rebalances which reference cannot be parsed as a unix timestamp
	// Then we include the parset timestamp on each rebalance
	const isUnixTimestampString = (str: string) => {
		const parsed = parseInt(str)
		if (isNaN(parsed)) return false
		return parsed.toString() === str
	}
	const rebalanceParsed = rebalances
		.filter((rebalance) => isUnixTimestampString(rebalance.reference))
		.map((rebalance) => {
			return {
				...rebalance,
				timestamp: parseInt(rebalance.reference),
			}
		})
	// now we filter ones between the dates
	const rebalancesInPeriod = rebalanceParsed.filter(
		(rebalance) =>
			rebalance.timestamp >= startDate && rebalance.timestamp <= endDate,
	)
	return rebalancesInPeriod
}

interface IContractsInMemoryDbView extends IMessDbView {
	listAllContracts: () => Promise<string[]>
	listAllStrategiesByContractAndUser: (
		uniqueContractKey: string,
		userWallet: string,
	) => Promise<string[]>
	getStrategyLastIdea: (
		uniqueStrategyReference: string,
	) => Promise<CONTRACT_INTERFACES.ITradeIdea>
	getStrategy: (
		uniqueStrategyReference: string,
	) => Promise<CONTRACT_INTERFACES.ITradeIdeaStrategy>
	getStrategyCreator: (
		uniqueStrategyReference: string,
	) => Promise<CONTRACT_INTERFACES.ITradeIdeaCreator>
	getCreator: (
		uniqueContractKey: string,
		creatorWallet: string,
	) => Promise<CONTRACT_INTERFACES.ITradeIdeaCreator>
	getStrategyWithCreator: (uniqueStrategyReference: string) => Promise<{
		strategy: CONTRACT_INTERFACES.ITradeIdeaStrategy
		creator: CONTRACT_INTERFACES.ITradeIdeaCreator
	}>
	listAllIdeasIndexesByStrategy: (
		uniqueStrategyKey: string,
	) => Promise<string[]>
	listAllIdeasKeysByStrategy: (
		uniqueStrategyKey: string,
	) => Promise<MyIdeaKeys[]>
	listAllIdeasIndexesByContract: (
		uniqueContractKey: string,
	) => Promise<string[]>
	listAllPublicIdeasIndexesByContract: (
		uniqueContractKey: string,
	) => Promise<string[]>
	listCreatedByIdeasIndexesByContract: (
		uniqueContractKey: string,
		creatorWallet: string,
	) => Promise<string[]>
	listOwnedByIdeasIndexesByContract: (
		uniqueContractKey: string,
		ownerWallet: string,
	) => Promise<string[]>
	listOwnedByIdeasKeysByContract: (
		uniqueContractKey: string,
		ownerWallet: string,
	) => Promise<MyIdeaKeys[]>
	getIdeaKeys: (ideaIndex: string) => Promise<MyIdeaKeys>
	getIdeaData: (ideaIndex: string) => Promise<CONTRACT_INTERFACES.ITradeIdea>
	listContractCreatorStrategies: (
		uniqueContractKey: string,
		creatorWallet: string,
	) => Promise<string[]>
	listContractUserWithAccessibleIdeasStrategies: (
		uniqueContractKey: string,
		userWallet: string,
	) => Promise<string[]>
	listContractUserSubscribedToIdeasStrategies: (
		uniqueContractKey: string,
		userWallet: string,
	) => Promise<string[]>
	listContractUserAccessibleIdeas: (
		uniqueContractKey: string,
		userWallet: string,
	) => Promise<number[]>
	listContractUserAccessibleIdeasByStrategy: (
		uniqueContractKey: string,
		userWallet: string,
		uniqueStrategyKey: string,
	) => Promise<number[]>
	listContractWithPublicIdeasStrategies: (
		uniqueContractKey: string,
	) => Promise<string[]>
	getStrategyPortfolio: (
		uniqueStrategyReference: string,
	) => Promise<IPortfolioView | 'ENCRYPTED'>
	getStrategyRebalances: (
		uniqueStrategyReference: string,
	) => Promise<IRebalanceMap[]>
	getStrategyRebalance: (
		uniqueStrategyReference: string,
		rebalanceReference: string,
	) => Promise<IRebalanceMap>
	getStrategyRebalancesInPeriod: (
		uniqueStrategyReference: string,
		startDate: number,
		endDate: number,
	) => Promise<IRebalanceMap[]>
}

const contractsInMemoryDbView: IContractsInMemoryDbView = {
	reduce,
	getStrategyLastIdea,
	getStrategy,
	getStrategyCreator,
	getCreator,
	getStrategyWithCreator,
	listAllStrategiesByContractAndUser,
	listAllIdeasIndexesByStrategy,
	listAllIdeasKeysByStrategy,
	listAllIdeasIndexesByContract,
	listAllPublicIdeasIndexesByContract,
	listCreatedByIdeasIndexesByContract,
	listOwnedByIdeasIndexesByContract,
	listOwnedByIdeasKeysByContract,
	getIdeaKeys,
	getIdeaData,
	listAllContracts,
	listContractWithPublicIdeasStrategies,
	listContractCreatorStrategies,
	listContractUserWithAccessibleIdeasStrategies,
	listContractUserSubscribedToIdeasStrategies,
	listContractUserAccessibleIdeasByStrategy,
	listContractUserAccessibleIdeas,
	getStrategyPortfolio,
	getStrategyRebalances,
	getStrategyRebalance,
	getStrategyRebalancesInPeriod,
}

export {
	IContractsInMemoryDbView,
	contractsInMemoryDbView,
	getContractsInMemoryState,
	setContractsInMemoryState,
}
