# standard imports
import logging

# third-party imports
from sqlalchemy import Column, desc, Integer, String
from sqlalchemy.dialects.postgresql import JSON
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.orm.session import Session

# local imports
from cic_ussd.db.models.base import SessionBase
from cic_ussd.error import VersionTooLowError

logg = logging.getLogger(__name__)


class UssdSession(SessionBase):
    __tablename__ = 'ussd_session'

    data = Column(JSON)
    external_session_id = Column(String, nullable=False, index=True, unique=True)
    msisdn = Column(String, nullable=False)
    service_code = Column(String, nullable=False)
    state = Column(String, nullable=False)
    user_input = Column(String)
    version = Column(Integer, nullable=False)

    def set_data(self, key, session, value):
        if self.data is None:
            self.data = {}
        self.data[key] = value

        # https://stackoverflow.com/questions/42559434/updates-to-json-field-dont-persist-to-db
        flag_modified(self, "data")
        session.add(self)

    def get_data(self, key):
        if self.data is not None:
            return self.data.get(key)
        else:
            return None

    def check_version(self, new_version):
        if new_version <= self.version:
            raise VersionTooLowError('New session version number is not greater than last saved version!')

    def update(self, user_input, state, version, session):
        self.check_version(version)
        self.user_input = user_input
        self.state = state
        self.version = version
        session.add(self)

    @staticmethod
    def has_record_for_phone_number(phone_number: str, session: Session):
        """
        :param phone_number:
        :type phone_number:
        :param session:
        :type session:
        :return:
        :rtype:
        """
        session = SessionBase.bind_session(session=session)
        ussd_session = session.query(UssdSession).filter_by(msisdn=phone_number).first()
        SessionBase.release_session(session=session)
        return ussd_session is not None

    @staticmethod
    def last_ussd_session(phone_number: str, session: Session):
        """
        :param phone_number:
        :type phone_number:
        :param session:
        :type session:
        :return:
        :rtype:
        """
        session = SessionBase.bind_session(session=session)
        ussd_session = session.query(UssdSession) \
            .filter_by(msisdn=phone_number) \
            .order_by(desc(UssdSession.created)) \
            .first()
        SessionBase.release_session(session=session)
        return ussd_session

    def to_json(self):
        """ This function serializes the in db ussd session object to a JSON object
        :return: A JSON object of a ussd session in db
        :rtype: dict
        """
        return {
            "data": self.data,
            "external_session_id": self.external_session_id,
            "msisdn": self.msisdn,
            "service_code": self.service_code,
            "state": self.state,
            "user_input": self.user_input,
            "version": self.version
        }