from lchttp.json import json_dumps, json_loads

from django.contrib import auth
from django.contrib.sessions.backends.db import SessionStore as DjangoDBStore
from django.utils import timezone

from console_sessions import (
    CREDENTIAL_ID_SESSION_KEY,
    SESSION_LAST_MODIFIED_KEY,
    IP_SESSION_KEY,
    LAST_MFA_SESSION_KEY,
    USER_AGENT_SESSION_KEY,
)


class SessionStore(DjangoDBStore):
    """
    Implements database backed sessions, with a foreign key to User.
    It also stores IP and User Agent.
    """

    def __init__(
        self,
        session_key=None,
        user_agent=None,
        ip=None,
        credential_id=None,
        last_mfa=None,
    ):
        self.user_agent = user_agent
        self.ip = ip
        self.credential_id = credential_id
        self.last_mfa = last_mfa
        super().__init__(session_key)

    @classmethod
    def get_model_class(cls):
        from console_sessions.models import Session

        return Session

    def load(self):
        data = super().load()
        if (
            data.get(USER_AGENT_SESSION_KEY) != self.user_agent
            or data.get(IP_SESSION_KEY) != self.ip
        ):
            # If IP or User Agent has changed, set modified to True in order to save
            # the new IP and User Agent
            self.modified = True
        return data

    def save(self, must_create=False):
        # Store IP and User Agent in session_data
        if USER_AGENT_SESSION_KEY not in self or self[USER_AGENT_SESSION_KEY] != self.user_agent:
            self[USER_AGENT_SESSION_KEY] = self.user_agent
        if IP_SESSION_KEY not in self or self[IP_SESSION_KEY] != self.ip:
            self[IP_SESSION_KEY] = self.ip

        self.save_mfa_properties()

        super().save(must_create)

    def save_mfa_properties(self):
        """
        MFA Properties will almost always be saved by mutating an existing session.
        """
        if CREDENTIAL_ID_SESSION_KEY in self and not self.credential_id:
            self.credential_id = self[CREDENTIAL_ID_SESSION_KEY]
        elif self.credential_id:
            self[CREDENTIAL_ID_SESSION_KEY] = self.credential_id

        if LAST_MFA_SESSION_KEY in self and not self.last_mfa:
            self.last_mfa = self[LAST_MFA_SESSION_KEY]
        elif self.last_mfa:
            self[LAST_MFA_SESSION_KEY] = self.last_mfa

    def create_model_instance(self, data):
        # Store User, User Agent, IP, Credential ID, and Last MFA in Session (in DB).

        # Save "last_modified" to session data,
        # to easily check recent activity in views.
        if self.modified:
            data[SESSION_LAST_MODIFIED_KEY] = timezone.now()

        try:
            session_data = self.encode(data)
        except TypeError:
            # Dump to json and back to convert any UUIDs that might be in the
            # session data as Django's default serializer doesn't support UUIDs.
            session_data = self.encode(json_loads(json_dumps(data)))

        return self.model(
            modified=data.get(SESSION_LAST_MODIFIED_KEY, timezone.now()),
            session_key=self._get_or_create_session_key(),
            session_data=session_data,
            expire_date=self.get_expiry_date(),
            user_id=self.get(auth.SESSION_KEY),
            user_agent=self.user_agent,
            ip=self.ip,
            credential_id=self.credential_id,
            last_mfa=self.last_mfa,
        )
