import {
	IStrategyChain,
	IdeaCreatedEvent,
	uniqueKeyJoin,
	uniqueKeySplit,
	CONTRACT_INTERFACES,
} from '../../../../'
import { IMessDbMaker } from '../interfaces/chunks/mess-db-maker.i'
import {
	MyDbViewState,
	contractsInMemoryDbView,
	getContractsInMemoryState,
	setContractsInMemoryState,
} from './in-memory/contracts.dbview'

interface MyStrategyData {
	strategyUniqueKey: string
	strategyChain: IStrategyChain
	strategyCache: CONTRACT_INTERFACES.IStrategyState
}

interface MyContractDb {
	contractUniqueKey: string
	lastBlockComputed: number
	strategiesByOldest: string[]
	invalidStrategiesByOldest: Map<string, string>
	strategies: Map<string, MyStrategyData>
}

const VIEWS = [contractsInMemoryDbView]

export interface InMemoryDbMakerState {
	contractDbs: Map<string, MyContractDb>
}

const state: InMemoryDbMakerState = {
	contractDbs: new Map<string, MyContractDb>(),
}

const getInMemoryState = async (): Promise<{
	local: InMemoryDbMakerState
	intern: MyDbViewState
}> => {
	return {
		local: state,
		intern: getContractsInMemoryState(),
	}
}

const setInMemoryState = async (_state: {
	local: InMemoryDbMakerState
	intern: MyDbViewState
}) => {
	state.contractDbs = _state.local.contractDbs
	setContractsInMemoryState(_state.intern)
}

const existsContractDb = async (
	contractUniqueKey: string,
): Promise<boolean> => {
	return state.contractDbs.has(contractUniqueKey)
}

const createContractDb = async (contractUniqueKey: string): Promise<void> => {
	state.contractDbs.set(contractUniqueKey, {
		contractUniqueKey,
		lastBlockComputed: 0,
		strategiesByOldest: [],
		invalidStrategiesByOldest: new Map<string, string>(),
		strategies: new Map<string, MyStrategyData>(),
	})
}

const getLastBlockComputed = async (
	contractUniqueKey: string,
): Promise<number> => {
	const contractDb = state.contractDbs.get(contractUniqueKey)
	if (!contractDb) {
		throw new Error(
			`Contract DB not found for address: ${contractUniqueKey}`,
		)
	}
	return contractDb.lastBlockComputed
}

const setLastBlockComputed = async (
	contractUniqueKey: string,
	blockNumber: number,
): Promise<void> => {
	const contractDb = state.contractDbs.get(contractUniqueKey)
	if (!contractDb) {
		throw new Error(
			`Contract DB not found for address: ${contractUniqueKey}`,
		)
	}
	contractDb.lastBlockComputed = blockNumber
}

const includeNewIdea = async (
	contractUniqueKey: string,
	newIdeaEvent: IdeaCreatedEvent,
) => {
	const contractDb = state.contractDbs.get(contractUniqueKey)!
	const decomposed = uniqueKeySplit(contractUniqueKey)
	const strategyUniqueKey = uniqueKeyJoin([
		decomposed[0],
		decomposed[1],
		newIdeaEvent.creatorAddress,
		newIdeaEvent.strategyKey,
	])
	let strategyData = contractDb.strategies.get(strategyUniqueKey)
	const viewsArgsToReduce = [
		contractUniqueKey,
		newIdeaEvent,
		strategyData!.strategyChain,
		strategyData!.strategyCache,
	] as [
		string,
		IdeaCreatedEvent,
		IStrategyChain,
		CONTRACT_INTERFACES.IStrategyState,
	]

	for (const view of VIEWS) {
		await view.reduce(...viewsArgsToReduce)
	}
}

const updateStrategyChain = async (
	strategyUniqueKey: string,
	strategyChain: IStrategyChain,
	strategyCache: CONTRACT_INTERFACES.IStrategyState,
) => {
	const decomposed = uniqueKeySplit(strategyUniqueKey)
	// console.log('decomposed')
	// console.log(decomposed)
	const contractUniqueKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const contractDb = state.contractDbs.get(contractUniqueKey)
	if (!contractDb) {
		throw new Error(
			`Contract DB not found for address: ${contractUniqueKey}`,
		)
	}
	// add this key to index if not there
	const index = contractDb.strategiesByOldest.indexOf(strategyUniqueKey)
	if (index === -1) {
		contractDb.strategiesByOldest.push(strategyUniqueKey)
	}
	// get strategy from memory if there or add if not
	let strategyData = contractDb.strategies.get(strategyUniqueKey)
	if (!strategyData) {
		strategyData = {
			strategyUniqueKey: strategyUniqueKey,
			strategyChain,
			strategyCache,
		}
		contractDb.strategies.set(strategyUniqueKey, strategyData)
	} else {
		strategyData.strategyChain = strategyChain
		strategyData.strategyCache = strategyCache
	}
	// console.log('strategyData')
	// console.log(strategyData)
}

const getStrategyChain = async (
	strategyUniqueKey: string,
): Promise<IStrategyChain | undefined> => {
	const decomposed = uniqueKeySplit(strategyUniqueKey)
	const contractUniqueKey = uniqueKeyJoin([decomposed[0], decomposed[1]])
	const contractDb = state.contractDbs.get(contractUniqueKey)
	if (!contractDb) {
		throw new Error(
			`Contract DB not found for address: ${contractUniqueKey}`,
		)
	}
	const strategyData = contractDb.strategies.get(strategyUniqueKey)
	if (!strategyData) {
		return undefined
	}
	return strategyData.strategyChain
}

const invalidateStrategyChain = async (
	strategyUniqueKey: string,
	message: string,
) => {
	const decomposed = uniqueKeySplit(strategyUniqueKey)
	const contractUniqueKey = uniqueKeyJoin([decomposed[0], decomposed[1]])

	const contractDb = state.contractDbs.get(contractUniqueKey)
	if (!contractDb) {
		throw new Error(
			`Contract DB not found for address: ${strategyUniqueKey}`,
		)
	}
	// remove this key from index if it is there
	const index = contractDb.strategiesByOldest.indexOf(strategyUniqueKey)
	if (index !== -1) {
		contractDb.strategiesByOldest.splice(index, 1)
	}
	// add this key to invalid index if not there
	const invalidHas =
		contractDb.invalidStrategiesByOldest.has(strategyUniqueKey)
	if (!invalidHas) {
		contractDb.invalidStrategiesByOldest.set(strategyUniqueKey, message)
	}
	// remove strategy from memory if there
	try {
		contractDb.strategies.delete(strategyUniqueKey)
	} catch (e) {}
}

const checkInvalidStrategyChain = async (strategyUniqueKey: string) => {
	const decomposed = uniqueKeySplit(strategyUniqueKey)
	const contractUniqueKey = uniqueKeyJoin([decomposed[0], decomposed[1]])

	const contractDb = state.contractDbs.get(contractUniqueKey)
	if (!contractDb) {
		throw new Error(
			`Contract DB not found for address: ${strategyUniqueKey}`,
		)
	}
	const invalidMessage =
		contractDb.invalidStrategiesByOldest.get(strategyUniqueKey)
	if (invalidMessage === undefined) {
		return false
	} else {
		return invalidMessage
	}
}

const InMemoryDbMaker: IMessDbMaker = {
	getLastBlockComputed,
	setLastBlockComputed,
	existsContractDb,
	createContractDb,
	invalidateStrategyChain,
	checkInvalidStrategyChain,
	updateStrategyChain,
	getStrategyChain,
	includeNewIdea,
	getState: getInMemoryState,
	setState: setInMemoryState,
}

export { InMemoryDbMaker, contractsInMemoryDbView }
