from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional

from watchmen_model.admin import Topic
from watchmen_model.common import DataPage
from .free_storage_types import FreeAggregatePager, FreeAggregator, FreeFinder, FreePager
from .storage_types import Entity, EntityDeleter, EntityDistinctValuesFinder, EntityFinder, EntityHelper, EntityId, \
	EntityIdHelper, EntityList, EntityPager, EntityStraightValuesFinder, EntityUpdater


class StorageSPI(ABC):
	@abstractmethod
	def connect(self) -> None:
		"""
		connect when not connected, or do nothing if connected.
		call close when want to connect again. connection is autocommit.
		this method can be called multiple times, if there is a connection existing, do nothing
		"""
		pass

	@abstractmethod
	def close(self) -> None:
		pass

	@abstractmethod
	def insert_one(self, one: Entity, helper: EntityHelper) -> None:
		pass

	@abstractmethod
	def insert_all(self, data: List[Entity], helper: EntityHelper) -> None:
		pass

	@abstractmethod
	def update_one(self, one: Entity, helper: EntityIdHelper) -> int:
		"""
		returns 0 when update none, or 1 when update one
		"""
		pass

	@abstractmethod
	def update_only(self, updater: EntityUpdater, peace_when_zero: bool = False) -> int:
		"""
		update only one, if update none or more than one item, raise exception
		"""
		pass

	@abstractmethod
	def update_only_and_pull(self, updater: EntityUpdater) -> Optional[Entity]:
		"""
		update only one, if update none or more than one item, raise exception
		return the one before update
		"""
		pass

	@abstractmethod
	def update(self, updater: EntityUpdater) -> int:
		pass

	@abstractmethod
	def update_and_pull(self, updater: EntityUpdater) -> EntityList:
		pass

	@abstractmethod
	def delete_by_id(self, entity_id: EntityId, helper: EntityIdHelper) -> int:
		"""
		returns 0 when delete none, or 1 when delete one
		"""
		pass

	@abstractmethod
	def delete_by_id_and_pull(self, entity_id: EntityId, helper: EntityIdHelper) -> Optional[Entity]:
		"""
		return deleted none when delete none, or deleted one when delete one
		"""
		pass

	@abstractmethod
	def delete_only(self, deleter: EntityDeleter) -> int:
		"""
		delete only one, if delete none or more than one item, raise exception
		"""
		pass

	@abstractmethod
	def delete_only_and_pull(self, deleter: EntityDeleter) -> Optional[Entity]:
		"""
		delete only one, if delete none or more than one item, raise exception
		return the one before delete
		"""
		pass

	@abstractmethod
	def delete(self, deleter: EntityDeleter) -> int:
		pass

	@abstractmethod
	def delete_and_pull(self, deleter: EntityDeleter) -> EntityList:
		pass

	@abstractmethod
	def find_by_id(self, entity_id: EntityId, helper: EntityIdHelper) -> Optional[Entity]:
		pass

	@abstractmethod
	def find_and_lock_by_id(self, entity_id: EntityId, helper: EntityIdHelper) -> Optional[Entity]:
		pass

	@abstractmethod
	def find_one(self, finder: EntityFinder) -> Optional[Entity]:
		pass

	@abstractmethod
	def find(self, finder: EntityFinder) -> EntityList:
		pass

	@abstractmethod
	def find_distinct_values(self, finder: EntityDistinctValuesFinder) -> EntityList:
		"""
		filled values with given distinct columns, returns an entity list.
		entity is deserialized by shaper
		"""
		pass

	@abstractmethod
	def find_straight_values(self, finder: EntityStraightValuesFinder) -> EntityList:
		"""
		fill values with given straight columns, returns an entity list
		entity will not be deserialized by shaper.
		And when there is aggregated columns, other columns will be used in group by
		"""
		pass

	@abstractmethod
	def find_all(self, helper: EntityHelper) -> EntityList:
		pass

	@abstractmethod
	def page(self, pager: EntityPager) -> DataPage:
		pass

	@abstractmethod
	def exists(self, finder: EntityFinder) -> bool:
		pass

	@abstractmethod
	def count(self, finder: EntityFinder) -> int:
		pass


class TransactionalStorageSPI(StorageSPI):
	"""
	Example:
		def a_func():
			# Declare a storage and begin it explicitly\n
			storage = TransactionStorageSPI()
			storage.begin()\n

			# Then do logic with transaction\n
			try:
				# do some logic\n
				storage.commit_and_close()
			except:
				storage.rollback_and_close()

			# Or do logic with no transaction.\n
			# Use this pattern only when no write logic included\n
			try:
				# do some read logic\n
			finally:
				storage.close()
	"""

	@abstractmethod
	def begin(self) -> None:
		pass

	@abstractmethod
	def commit_and_close(self) -> None:
		"""
		1. commit successfully -> close
		2. commit failed -> throw exception
		"""
		pass

	@abstractmethod
	def rollback_and_close(self) -> None:
		"""
		1. rollback successfully -> close
		2. rollback failed -> close
		"""
		pass


class TopicDataStorageSPI(TransactionalStorageSPI):
	@abstractmethod
	def register_topic(self, topic: Topic) -> None:
		pass

	@abstractmethod
	def create_topic_entity(self, topic: Topic) -> None:
		pass

	@abstractmethod
	def update_topic_entity(self, topic: Topic, original_topic: Topic) -> None:
		pass

	@abstractmethod
	def drop_topic_entity(self, topic_name: str) -> None:
		pass

	@abstractmethod
	def truncate(self, helper: EntityHelper) -> None:
		pass

	# noinspection PyMethodMayBeStatic
	def is_free_find_supported(self) -> bool:
		return True

	def append_topic_to_trino(self, topic: Topic) -> None:
		pass

	def drop_topic_from_trino(self, topic: Topic) -> None:
		pass

	@abstractmethod
	def free_find(self, finder: FreeFinder) -> List[Dict[str, Any]]:
		pass

	@abstractmethod
	def free_page(self, pager: FreePager) -> DataPage:
		pass

	@abstractmethod
	def free_aggregate_find(self, aggregator: FreeAggregator) -> List[Dict[str, Any]]:
		pass

	@abstractmethod
	def free_aggregate_page(self, pager: FreeAggregatePager) -> DataPage:
		pass
