from __future__ import annotations

import time
from collections.abc import Callable
from typing import Any

from opentelemetry import trace
from opentelemetry.semconv._incubating.attributes.session_attributes import SESSION_ID

from plain.http import Response
from plain.runtime import settings
from plain.utils.cache import patch_vary_headers
from plain.utils.http import http_date

from .core import SessionStore
from .requests import get_request_session, set_request_session


class SessionMiddleware:
    def __init__(self, get_response: Callable[[Any], Any]) -> None:
        self.get_response = get_response

    def __call__(self, request: Any) -> Response:
        session_key = request.cookies.get(settings.SESSION_COOKIE_NAME)

        session = SessionStore(session_key)
        set_request_session(request, session)

        if session.model_instance:
            trace.get_current_span().set_attribute(
                SESSION_ID, session.model_instance.id
            )

        response = self.get_response(request)

        """
        If request.session was modified, or if the configuration is to save the
        session every time, save the changes and set a session cookie or delete
        the session cookie if the session has been emptied.
        """
        session = get_request_session(request)
        accessed = session.accessed
        modified = session.modified
        empty = session.is_empty()

        # First check if we need to delete this cookie.
        # The session should be deleted only if the session is entirely empty.
        if settings.SESSION_COOKIE_NAME in request.cookies and empty:
            response.delete_cookie(
                settings.SESSION_COOKIE_NAME,
                path=settings.SESSION_COOKIE_PATH,
                domain=settings.SESSION_COOKIE_DOMAIN,
                samesite=settings.SESSION_COOKIE_SAMESITE,
            )
            patch_vary_headers(response, ["Cookie"])
        else:
            if accessed:
                patch_vary_headers(response, ["Cookie"])
            if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
                if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
                    max_age = None
                    expires = None
                else:
                    max_age = settings.SESSION_COOKIE_AGE
                    expires_time = time.time() + max_age
                    expires = http_date(expires_time)
                # Save the session data and refresh the client cookie.
                # Skip session save for 5xx responses.
                if response.status_code < 500:
                    session.save()
                    response.set_cookie(
                        settings.SESSION_COOKIE_NAME,
                        session.session_key,
                        max_age=max_age,
                        expires=expires,
                        domain=settings.SESSION_COOKIE_DOMAIN,
                        path=settings.SESSION_COOKIE_PATH,
                        secure=settings.SESSION_COOKIE_SECURE or None,
                        httponly=settings.SESSION_COOKIE_HTTPONLY or None,
                        samesite=settings.SESSION_COOKIE_SAMESITE,
                    )
        return response
