import httpx
import json
import re

from django.conf import settings
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import (
    HTTP_200_OK,
    HTTP_201_CREATED,
    HTTP_400_BAD_REQUEST,
    HTTP_403_FORBIDDEN,
)
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet

from apps.accounts.models import Organization, Role
from apps.accounts.permissions import CanManageOrganization, can_manage_organization
from apps.onboarding.consumers.prompts import OnboardingPrompts
from apps.onboarding.mixins import RequestParamMixin
from apps.onboarding.models import AgentConfig, AgentInteractionLog, OnboardingFlow, OnboardingSession
from apps.onboarding.serializers import (
    AgentConfigSerializer,
    AgentInteractionLogSerializer,
    OnboardingFlowSerializer,
    OnboardingSessionSerializer,
)


class OnboardingFlowViewSet(RequestParamMixin, ModelViewSet):
    queryset = OnboardingFlow.objects.all()
    serializer_class = OnboardingFlowSerializer
    permission_classes = [IsAuthenticated]
    lookup_field = 'uuid'

    def get_permissions(self):
        permissions = super().get_permissions()
        if self.action in ['update', 'partial_update', 'destroy']:
            return [*permissions, CanManageOrganization()]
        return permissions

    def get_queryset(self):
        user = self.request.user
        queryset = OnboardingFlow.objects.filter(
            Q(role__organization__owner=user) |
            Q(role__organization__members=user)
        ).distinct().order_by('-created_at')

        if organization_uuid := self._get_param('organization_uuid'):
            queryset = queryset.filter(role__organization__uuid=organization_uuid)

        if role_uuid := self._get_param('role_uuid'):
            queryset = queryset.filter(role__uuid=role_uuid)

        return queryset

    def create(self, request, *args, **kwargs):
        role_uuid = request.data.get('role_uuid')
        if not role_uuid:
            raise ValidationError({'role_uuid': 'role_uuid is required.'})

        role = Role.objects.filter(uuid=role_uuid).first()
        if not role:
            raise NotFound('Role not found')
        if not can_manage_organization(request.user, role.organization):
            raise PermissionDenied('Forbidden')

        is_active = self._parse_bool(request.data.get('is_active'), default=True, field_name='is_active')

        flow = OnboardingFlow.objects.create(
            title=(request.data.get('title') or f'Onboarding Flow: {role.name}').strip(),
            role=role,
            structure=request.data.get('structure') or [],
            is_active=is_active,
        )

        serializer = self.get_serializer(flow)
        return Response(serializer.data, status=HTTP_201_CREATED)

    def update(self, request, *args, **kwargs):
        flow = self.get_object()
        title = request.data.get('title')
        structure = request.data.get('structure')
        is_active = request.data.get('is_active')

        if title is not None:
            flow.title = str(title).strip()
        if structure is not None:
            flow.structure = structure if isinstance(structure, list) else flow.structure
        if is_active is not None:
            flow.is_active = self._parse_bool(is_active, field_name='is_active')

        flow.save(update_fields=['title', 'structure', 'is_active', 'updated_at'])
        serializer = self.get_serializer(flow)
        return Response(serializer.data, status=HTTP_200_OK)

    def partial_update(self, request, *args, **kwargs):
        flow = self.get_object()

        if 'title' in request.data:
            flow.title = str(request.data.get('title') or '').strip()
        if 'structure' in request.data:
            structure = request.data.get('structure')
            if not isinstance(structure, list):
                raise ValidationError({'structure': 'structure must be a list'})
            flow.structure = structure
        if 'is_active' in request.data:
            flow.is_active = self._parse_bool(request.data.get('is_active'), field_name='is_active')

        flow.save(update_fields=['title', 'structure', 'is_active', 'updated_at'])
        serializer = self.get_serializer(flow)
        return Response(serializer.data, status=HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        flow = self.get_object()
        with transaction.atomic():
            OnboardingSession.objects.filter(flow=flow).delete()
            self.perform_destroy(flow)

        return Response(status=204)

    def _parse_bool(self, value, *, default=None, field_name='value'):
        if value is None:
            if default is not None:
                return default
            raise ValidationError({field_name: f'{field_name} is required'})

        if isinstance(value, bool):
            return value

        if isinstance(value, int):
            if value in (0, 1):
                return bool(value)
            raise ValidationError({field_name: f'{field_name} must be a boolean value'})

        if isinstance(value, str):
            normalized = value.strip().lower()
            if normalized in ['true', '1', 'yes', 'on']:
                return True
            if normalized in ['false', '0', 'no', 'off']:
                return False

        raise ValidationError({field_name: f'{field_name} must be a boolean value'})

    @action(detail=True, methods=['post'], url_path='start-session')
    def start_session(self, request, uuid=None):
        flow = self.get_object()

        if not request.user.is_manager and not flow.role.members.filter(id=request.user.id).exists():
            return Response(
                {'error': 'Join this role before starting onboarding.'},
                status=HTTP_403_FORBIDDEN,
            )

        session = OnboardingSession.objects.filter(user=request.user, role=flow.role, flow=flow).first()

        if not session:
            session = OnboardingSession.objects.filter(user=request.user, role=flow.role).first()
            if session:
                state = session.state or {}
                state['flow_uuid'] = str(flow.uuid)
                session.flow = flow
                session.state = state
                session.save(update_fields=['flow', 'state', 'updated_at'])
                serializer = OnboardingSessionSerializer(session)
                return Response(serializer.data, status=HTTP_200_OK)

            session = OnboardingSession.objects.create(
                user=request.user,
                role=flow.role,
                flow=flow,
                status='active',
                state={
                    'progress': 0,
                    'current_step': 'intro',
                },
                active_configs={},
            )
            serializer = OnboardingSessionSerializer(session)
            return Response(serializer.data, status=HTTP_201_CREATED)

        serializer = OnboardingSessionSerializer(session)
        return Response(serializer.data, status=HTTP_200_OK)


class AgentConfigViewSet(RequestParamMixin, ModelViewSet):
    queryset = AgentConfig.objects.all()
    serializer_class = AgentConfigSerializer
    permission_classes = [IsAuthenticated]
    lookup_field = 'uuid'

    def get_permissions(self):
        permissions = super().get_permissions()
        if self.action in ['update', 'partial_update', 'destroy']:
            return [*permissions, CanManageOrganization()]
        return permissions

    def get_queryset(self):
        queryset = AgentConfig.objects.filter(
            Q(organization__owner=self.request.user) | Q(organization__members=self.request.user)
        ).distinct().order_by('-updated_at')

        if organization_uuid := self._get_param('organization_uuid'):
            queryset = queryset.filter(organization__uuid=organization_uuid)

        if role_uuid := self._get_param('role_uuid'):
            queryset = queryset.filter(role__uuid=role_uuid)

        return queryset

    def create(self, request, *args, **kwargs):
        organization_uuid = self._get_param('organization_uuid')
        if not organization_uuid:
            raise ValidationError({'organization_uuid': 'organization_uuid is required.'})

        organization = Organization.objects.filter(uuid=organization_uuid).filter(
            Q(owner=request.user) | Q(members=request.user),
        ).first()
        if not organization:
            raise NotFound('Organization not found')
        if not can_manage_organization(request.user, organization):
            raise PermissionDenied('Forbidden')

        name = str(request.data.get('name') or '').strip()
        if not name:
            raise ValidationError({'name': 'Name is required.'})

        agent_type = str(request.data.get('agent_type') or '').strip()
        if not agent_type:
            raise ValidationError({'agent_type': 'agent_type is required.'})

        role_uuid = request.data.get('role_uuid')
        role = None
        if role_uuid:
            role = Role.objects.filter(uuid=role_uuid, organization=organization).first()
            if not role:
                raise NotFound('Role not found in this organization')

        config = AgentConfig.objects.create(
            organization=organization,
            role=role,
            name=name,
            agent_type=agent_type,
            system_prompt=str(request.data.get('system_prompt') or ''),
            llm_config=request.data.get('llm_config') or {},
        )

        serializer = self.get_serializer(config)
        return Response(serializer.data, status=HTTP_201_CREATED)

    def update(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def partial_update(self, request, *args, **kwargs):
        config = self.get_object()
        fields = ['name', 'agent_type', 'system_prompt', 'llm_config']
        for field in fields:
            if field in request.data and request.data.get(field) is not None:
                setattr(config, field, request.data[field])

        config.save(update_fields=fields + ['updated_at'])
        serializer = self.get_serializer(config)
        return Response(serializer.data, status=HTTP_200_OK)


class OnboardingSessionViewSet(RequestParamMixin, ModelViewSet):
    queryset = OnboardingSession.objects.all()
    serializer_class = OnboardingSessionSerializer
    permission_classes = [IsAuthenticated]
    lookup_field = 'uuid'

    def get_queryset(self):
        user = self.request.user
        if user.is_manager:
            queryset = OnboardingSession.objects.filter(role__organization__members=user).distinct()
        else:
            queryset = OnboardingSession.objects.filter(user=user)

        if organization_uuid := self._get_param('organization_uuid'):
            queryset = queryset.filter(role__organization__uuid=organization_uuid)

        if role_uuid := self._get_param('role_uuid'):
            queryset = queryset.filter(role__uuid=role_uuid)

        if user_uuid := self._get_param('user_uuid'):
            if not user.is_manager and str(user.uuid) != str(user_uuid):
                raise PermissionDenied('You can only view your own progress sessions.')
            queryset = queryset.filter(user__uuid=user_uuid)

        if flow_uuid := self._get_param('flow_uuid'):
            queryset = queryset.filter(flow__uuid=flow_uuid)

        if status_value := self.request.query_params.get('status'):
            queryset = queryset.filter(status=status_value)

        return queryset.order_by('-created_at')

    @action(detail=False, methods=['get'], url_path='progress-overview')
    def progress_overview(self, request):
        user = request.user

        role_uuid = request.query_params.get('role_uuid')

        if user.is_manager:
            roles_qs = Role.objects.filter(
                Q(organization__owner=user) | Q(organization__members=user)
            ).distinct()
        else:
            roles_qs = Role.objects.filter(members=user)

        if role_uuid:
            roles_qs = roles_qs.filter(uuid=role_uuid)

        rows = []

        for role in roles_qs.order_by('name'):
            flows = list(OnboardingFlow.objects.filter(role=role).order_by('-updated_at'))
            if not flows:
                continue

            if user.is_manager:
                learners = list(role.members.all().order_by('first_name', 'last_name', 'email_address'))
            else:
                learners = [user] if role.members.filter(id=user.id).exists() else []

            if not learners:
                continue

            role_sessions = list(
                OnboardingSession.objects.filter(role=role, user__in=learners)
                .select_related('user')
                .order_by('-updated_at')
            )

            latest_by_user_flow = {}
            for session in role_sessions:
                if not session.flow_id:
                    continue

                key = (session.user_id, str(session.flow.uuid))
                if key not in latest_by_user_flow:
                    latest_by_user_flow[key] = session

            for flow in flows:
                flow_uuid_str = str(flow.uuid)
                for learner in learners:
                    latest_session = latest_by_user_flow.get((learner.id, flow_uuid_str))

                    latest_status = latest_session.status if latest_session else 'not_started'
                    state = latest_session.state if latest_session and isinstance(latest_session.state, dict) else {}
                    progress = state.get('progress_percentage', state.get('progress', 0))

                    rows.append({
                        'role': {
                            'uuid': str(role.uuid),
                            'name': role.name,
                        },
                        'user': {
                            'uuid': str(learner.uuid),
                            'name': str(getattr(learner, 'full_name', '')).strip() or learner.email_address,
                            'email': learner.email_address,
                        },
                        'flow': {
                            'uuid': flow_uuid_str,
                            'title': flow.title,
                            'is_active': flow.is_active,
                        },
                        'latest_status': latest_status,
                        'progress': int(progress) if isinstance(progress, (int, float)) else 0,
                        'is_completed': latest_status == 'completed',
                        'latest_session_uuid': str(latest_session.uuid) if latest_session else None,
                        'updated_at': latest_session.updated_at.isoformat() if latest_session and latest_session.updated_at else None,
                    })

        rows.sort(
            key=lambda row: (
                str(row.get('role', {}).get('name', '')),
                str(row.get('user', {}).get('name', '')),
                str(row.get('flow', {}).get('title', '')),
            )
        )

        return Response(rows, status=HTTP_200_OK)

    def create(self, request, *args, **kwargs):
        return Response(
            {'error': 'Use onboarding-flow/<uuid>/start-session/ to create a session.'},
            status=HTTP_400_BAD_REQUEST,
        )

    def _has_attempt(self, value):
        if value is None:
            return False
        if isinstance(value, str):
            return bool(value.strip())
        if isinstance(value, (list, dict, tuple, set)):
            return bool(value)
        return True

    def _get_flow_for_session(self, session):
        return session.flow

    def _get_page_from_flow(self, flow, page_uuid):
        pages = flow.structure if isinstance(flow.structure, list) else []
        page = next(
            (
                item for item in pages
                if isinstance(item, dict) and str(item.get('uuid')) == str(page_uuid)
            ),
            None,
        )
        return page, pages

    def _upsert_page_responses(self, session, page_uuid, responses):
        state = session.state or {}
        stored_responses = state.get('responses', {})
        if not isinstance(stored_responses, dict):
            stored_responses = {}

        stored_responses[str(page_uuid)] = responses
        state['responses'] = stored_responses
        state['last_page_uuid'] = str(page_uuid)
        session.state = state
        session.save(update_fields=['state', 'updated_at'])

    def _record_page_visit(self, session, page_uuid):
        state = session.state or {}
        visited_pages = state.get('visited_pages', [])
        if not isinstance(visited_pages, list):
            visited_pages = []

        page_uuid_str = str(page_uuid)
        if page_uuid_str not in visited_pages:
            visited_pages.append(page_uuid_str)

        state['visited_pages'] = visited_pages
        state['last_page_uuid'] = page_uuid_str
        session.state = state
        session.save(update_fields=['state', 'updated_at'])

    def _mark_page_progress(self, session, page_uuid, total_pages, is_final_quiz=False):
        if is_final_quiz:
            return

        state = session.state or {}
        completed_modules = state.get('completed_modules', [])
        if not isinstance(completed_modules, list):
            completed_modules = []

        page_uuid_str = str(page_uuid)
        if page_uuid_str not in completed_modules:
            completed_modules.append(page_uuid_str)

        state['completed_modules'] = completed_modules
        completed_count = len(completed_modules)
        state['progress_percentage'] = int(round((completed_count / total_pages) * 100)) if total_pages else 0

        session.state = state
        session.save(update_fields=['state', 'updated_at'])

    def _build_system_prompt(self, config):
        return (config and config.system_prompt) or OnboardingPrompts.FALLBACK_SYSTEM_PROMPT

    def _get_agent_config(self, session, agent_type):
        role_specific = AgentConfig.objects.filter(
            role=session.role,
            agent_type=agent_type,
        ).order_by('-updated_at').first()
        if role_specific:
            return role_specific

        return AgentConfig.objects.filter(
            organization=session.role.organization,
            role__isnull=True,
            agent_type=agent_type,
        ).order_by('-updated_at').first()

    def _extract_json_object(self, text):
        if not text:
            return None

        candidate = str(text).strip()

        try:
            return json.loads(candidate)
        except Exception:
            pass

        matches = re.findall(r'```(?:json)?\s*([\s\S]*?)```', candidate, re.IGNORECASE)
        for block in matches:
            try:
                return json.loads(block.strip())
            except Exception:
                continue

        decoder = json.JSONDecoder()
        for idx, char in enumerate(candidate):
            if char != '{':
                continue
            try:
                obj, _ = decoder.raw_decode(candidate[idx:])
                if isinstance(obj, dict):
                    return obj
            except Exception:
                continue

        return None

    def _grade_final_quiz_with_assessment_agent(self, session, quiz_fields, page_responses, pass_mark):
        select_results = []
        ai_fields = []
        select_correct_count = 0

        for field in quiz_fields:
            if not isinstance(field, dict):
                continue

            key = str(field.get('key') or '').strip()
            if not key:
                continue

            field_type = str(field.get('field_type') or '').strip().lower()
            validation = field.get('validation') if isinstance(field.get('validation'), dict) else {}

            if field_type == 'select':
                correct_option = str(validation.get('correct_option') or '').strip()
                if correct_option:
                    answer = page_responses.get(key)
                    attempted = self._has_attempt(answer)
                    answer_text = str(answer).strip() if attempted else ''
                    is_correct = attempted and answer_text == correct_option
                    if is_correct:
                        select_correct_count += 1

                    select_results.append(
                        {
                            'key': key,
                            'correct': is_correct,
                            'reason': '' if is_correct else 'Selected option does not match the expected choice.',
                        }
                    )
                    continue

            ai_fields.append(field)

        ai_correct_count = 0
        ai_gradable_count = 0
        ai_per_question = []

        if ai_fields:
            config = self._get_agent_config(session, 'assessment') or self._get_agent_config(session, 'knowledge')
            if not config:
                return None, {'error': 'No assessment/knowledge agent configured for grading.'}

            prompt = OnboardingPrompts.grading_prompt(ai_fields, page_responses)

            try:
                with httpx.Client(timeout=60.0) as client:
                    response = client.post(
                        settings.INFERENCE_CHAT_COMPLETIONS_ENDPOINT,
                        json={
                            'model': (config.llm_config or {}).get('model_id', 'meta-llama-3.1-8b'),
                            'messages': [
                                {'role': 'system', 'content': self._build_system_prompt(config)},
                                {'role': 'user', 'content': prompt},
                            ],
                            'max_tokens': 1000,
                        },
                    )
                    response.raise_for_status()
                    content = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
            except Exception:
                return None, {'error': 'Assessment grading model unavailable.'}

            parsed = self._extract_json_object(content)
            if not isinstance(parsed, dict):
                return None, {'error': 'Assessment grading returned invalid JSON.'}

            try:
                ai_correct_count = int(parsed.get('correct_count', 0))
                ai_gradable_count = int(parsed.get('gradable_count', len(ai_fields)))
            except Exception:
                return None, {'error': 'Assessment grading returned invalid counts.'}

            if ai_gradable_count < 0:
                ai_gradable_count = 0
            if ai_correct_count < 0:
                ai_correct_count = 0
            if ai_correct_count > ai_gradable_count:
                ai_correct_count = ai_gradable_count

            ai_per_question = parsed.get('per_question', []) if isinstance(parsed.get('per_question', []), list) else []

        correct_count = select_correct_count + ai_correct_count
        gradable_count = len(select_results) + ai_gradable_count
        score_percentage = int(round((correct_count / gradable_count) * 100)) if gradable_count else 0

        merged_per_question = list(select_results) + list(ai_per_question)
        return {
            'correct_count': correct_count,
            'gradable_count': gradable_count,
            'score_percentage': score_percentage,
            'pass_mark': pass_mark,
            'per_question': merged_per_question,
        }, None

    def _sanitize_grading_details(self, quiz_fields, per_question):
        if not isinstance(per_question, list):
            return []

        validation_tokens_by_key = {}
        for field in quiz_fields:
            if not isinstance(field, dict):
                continue

            key = str(field.get('key') or '').strip()
            if not key:
                continue

            validation = field.get('validation') if isinstance(field.get('validation'), dict) else {}
            tokens = []

            correct_option = str(validation.get('correct_option') or '').strip()
            if correct_option:
                tokens.append(correct_option.lower())

            accepted_answers = validation.get('accepted_answers')
            if isinstance(accepted_answers, list):
                for answer in accepted_answers:
                    answer_text = str(answer or '').strip().lower()
                    if answer_text:
                        tokens.append(answer_text)

            validation_tokens_by_key[key] = tokens

        sanitized = []
        for item in per_question:
            if not isinstance(item, dict):
                continue

            key = str(item.get('key') or '').strip()
            correct = bool(item.get('correct'))
            raw_reason = str(item.get('reason') or '').strip()

            reason = raw_reason
            if not correct and reason:
                lowered = reason.lower()
                has_leak = any(token and token in lowered for token in validation_tokens_by_key.get(key, []))
                if has_leak:
                    reason = 'Your response missed key requirements from the prompt. Review the guidance and try again.'

            sanitized.append(
                {
                    'key': key,
                    'correct': correct,
                    'reason': reason,
                }
            )

        return sanitized

    def _evaluate_final_quiz(self, session):
        flow = self._get_flow_for_session(session)
        if not flow:
            return None, {'error': 'Onboarding flow not found for this session.'}

        pages = flow.structure if isinstance(flow.structure, list) else []
        if not pages:
            return None, {'error': 'Onboarding flow has no pages.'}

        quiz_page = None
        for page in pages:
            if not isinstance(page, dict):
                continue
            meta = page.get('meta') if isinstance(page.get('meta'), dict) else {}
            if str(meta.get('page_type') or '').strip() == 'final_quiz':
                quiz_page = page
                break

        if quiz_page is None:
            quiz_page = pages[-1] if isinstance(pages[-1], dict) else None

        if not isinstance(quiz_page, dict):
            return None, {'error': 'Final quiz page could not be identified.'}

        quiz_fields = quiz_page.get('fields') if isinstance(quiz_page.get('fields'), list) else []
        if not quiz_fields:
            return None, {'error': 'Final quiz has no questions.'}

        meta = quiz_page.get('meta') if isinstance(quiz_page.get('meta'), dict) else {}
        pass_mark = meta.get('pass_mark', 80)
        try:
            pass_mark = int(pass_mark)
        except Exception:
            pass_mark = 80

        state = session.state or {}
        responses = state.get('responses', {})
        if not isinstance(responses, dict):
            responses = {}

        page_uuid = str(quiz_page.get('uuid') or '')
        page_responses = responses.get(page_uuid, {})
        if not isinstance(page_responses, dict):
            page_responses = {}

        required_count = 0
        missing_required_keys = []

        for field in quiz_fields:
            if not isinstance(field, dict):
                continue

            key = str(field.get('key') or '').strip()
            if not key:
                continue

            answer = page_responses.get(key)
            attempted = self._has_attempt(answer)

            if bool(field.get('required', False)):
                required_count += 1
                if not attempted:
                    missing_required_keys.append(key)

        grading, grading_error = self._grade_final_quiz_with_assessment_agent(
            session,
            quiz_fields,
            page_responses,
            pass_mark,
        )
        if grading_error:
            return None, grading_error

        score_percentage = int(grading.get('score_percentage', 0))
        correct_count = int(grading.get('correct_count', 0))
        gradable_count = int(grading.get('gradable_count', 0))
        passed = len(missing_required_keys) == 0 and gradable_count > 0 and score_percentage >= pass_mark

        grading_details = self._sanitize_grading_details(quiz_fields, grading.get('per_question', []))

        quiz_result = {
            'page_uuid': page_uuid,
            'pass_mark': pass_mark,
            'required_count': required_count,
            'missing_required_keys': missing_required_keys,
            'gradable_count': gradable_count,
            'correct_count': correct_count,
            'score_percentage': score_percentage,
            'passed': passed,
            'grading_details': grading_details,
        }

        state['final_quiz_result'] = quiz_result
        session.state = state
        session.save(update_fields=['state', 'updated_at'])

        return quiz_result, None

    def _call_ka(self, session, page, message):
        config = self._get_agent_config(session, 'knowledge')
        system_prompt = self._build_system_prompt(config)
        page_body = str(page.get('body') or '') if isinstance(page, dict) else ''
        page_title = str(page.get('title') or '') if isinstance(page, dict) else ''
        context = f"Page: {page_title}\n\n{page_body}" if page_body else page_title
        prompt = f"Context:\n{context}\n\nQuestion: {message}"
        try:
            with httpx.Client(timeout=60.0) as client:
                model_id = (config.llm_config or {}).get('model_id', 'meta-llama-3.1-8b') if config else 'meta-llama-3.1-8b'
                response = client.post(
                    settings.INFERENCE_CHAT_COMPLETIONS_ENDPOINT,
                    json={
                        'model': model_id,
                        'messages': [
                            {'role': 'system', 'content': system_prompt},
                            {'role': 'user', 'content': prompt},
                        ],
                        'max_tokens': 512,
                    },
                )
                response.raise_for_status()
                return response.json().get('choices', [{}])[0].get('message', {}).get('content', '') or 'No answer available.'
        except Exception:
            return 'Knowledge agent is temporarily unavailable. Please try again later.'

    def _run_ka_page_revision(self, session, page, message):
        config = self._get_agent_config(session, 'knowledge')
        system_prompt = self._build_system_prompt(config)
        page_body = str(page.get('body') or '') if isinstance(page, dict) else ''
        page_title = str(page.get('title') or '') if isinstance(page, dict) else ''
        prompt = (
            f"You are revising onboarding page content to incorporate a learner's clarification request.\n\n"
            f"Page Title: {page_title}\n"
            f"Original Body:\n{page_body}\n\n"
            f"Learner's request: {message}\n\n"
            f"Rewrite the page body to address the learner's request while preserving the original content structure. "
            f"Return only the revised page body."
        )
        try:
            with httpx.Client(timeout=60.0) as client:
                model_id = (config.llm_config or {}).get('model_id', 'meta-llama-3.1-8b') if config else 'meta-llama-3.1-8b'
                response = client.post(
                    settings.INFERENCE_CHAT_COMPLETIONS_ENDPOINT,
                    json={
                        'model': model_id,
                        'messages': [
                            {'role': 'system', 'content': system_prompt},
                            {'role': 'user', 'content': prompt},
                        ],
                        'max_tokens': 1024,
                    },
                )
                response.raise_for_status()
                return response.json().get('choices', [{}])[0].get('message', {}).get('content', '') or page_body
        except Exception:
            return page_body

    @action(detail=True, methods=['post'], url_path='ask-ka')
    def ask_ka(self, request, uuid=None):
        session = self.get_object()
        page_uuid = request.data.get('page_uuid')
        message = request.data.get('message')
        mode = request.data.get('mode', 'separate')

        if not page_uuid or not message:
            return Response({'error': 'page_uuid and message are required.'}, status=HTTP_400_BAD_REQUEST)

        flow = self._get_flow_for_session(session)
        page = None
        if flow:
            page, _ = self._get_page_from_flow(flow, page_uuid)

        if mode == 'update_page':
            revised_body = self._run_ka_page_revision(session, page, message)
            state = session.state or {}
            overrides = state.get('page_overrides', {})
            if not isinstance(overrides, dict):
                overrides = {}
            overrides[str(page_uuid)] = revised_body
            state['page_overrides'] = overrides
            session.state = state
            session.save(update_fields=['state', 'updated_at'])
            return Response({
                'status': 'ok',
                'updated_page': True,
                'revised_page_body': revised_body,
                'session_state': session.state,
            })

        answer = self._call_ka(session, page, message)
        state = session.state or {}
        page_help = state.get('page_help', {})
        if not isinstance(page_help, dict):
            page_help = {}
        page_help[str(page_uuid)] = answer
        state['page_help'] = page_help
        session.state = state
        session.save(update_fields=['state', 'updated_at'])
        return Response({
            'status': 'ok',
            'answer': answer,
            'session_state': session.state,
        })

    @action(detail=True, methods=['post'], url_path='interact')
    def interact(self, request, uuid=None):
        session = self.get_object()
        user_message = request.data.get('message')
        page_uuid = request.data.get('page_uuid')
        responses = request.data.get('responses')

        if not user_message and not page_uuid:
            return Response({'error': 'Message or page_uuid is required'}, status=HTTP_400_BAD_REQUEST)

        if page_uuid:
            self._record_page_visit(session, page_uuid)

        if isinstance(responses, dict):
            flow = self._get_flow_for_session(session)
            total_pages = len(flow.structure) if flow and isinstance(flow.structure, list) else 0
            if page_uuid:
                self._upsert_page_responses(session, page_uuid, responses)
                is_final_quiz = False
                if flow:
                    page, _ = self._get_page_from_flow(flow, page_uuid)
                    if isinstance(page, dict):
                        meta = page.get('meta') if isinstance(page.get('meta'), dict) else {}
                        is_final_quiz = str(meta.get('page_type') or '').strip() == 'final_quiz'
                if total_pages:
                    self._mark_page_progress(session, page_uuid, total_pages, is_final_quiz=is_final_quiz)
            else:
                state = session.state or {}
                stored_responses = state.get('responses', {})
                if not isinstance(stored_responses, dict):
                    stored_responses = {}
                stored_responses.update(responses)
                state['responses'] = stored_responses
                session.state = state
                session.save(update_fields=['state', 'updated_at'])

        if user_message or isinstance(responses, dict):
            AgentInteractionLog.objects.create(
                session=session,
                sender_type='user',
                content=user_message or f'Submitted onboarding responses for page {page_uuid or "unknown"}',
                tool_call_metadata={'page_uuid': page_uuid, 'has_responses': isinstance(responses, dict)}
            )

        return Response({
            'status': 'received',
            'session_state': session.state,
        })

    @action(detail=True, methods=['get'], url_path='history')
    def history(self, request, uuid=None):
        session = self.get_object()
        logs = session.logs.all().order_by('created_at')
        serializer = AgentInteractionLogSerializer(logs, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'], url_path='complete')
    def complete(self, request, uuid=None):
        session = self.get_object()

        quiz_result, quiz_error = self._evaluate_final_quiz(session)
        if quiz_error:
            return Response(quiz_error, status=HTTP_400_BAD_REQUEST)

        if not quiz_result.get('passed'):
            return Response({
                'error': 'Final quiz pass mark not met.',
                'quiz_result': quiz_result,
            }, status=HTTP_400_BAD_REQUEST)

        state = session.state or {}
        completed_modules = state.get('completed_modules', [])
        if not isinstance(completed_modules, list):
            completed_modules = []
        if quiz_result.get('page_uuid') and quiz_result['page_uuid'] not in completed_modules:
            completed_modules.append(quiz_result['page_uuid'])
        state['completed_modules'] = completed_modules
        flow = self._get_flow_for_session(session)
        total_pages = len(flow.structure) if flow and isinstance(flow.structure, list) else 0
        state['progress_percentage'] = int(round((len(completed_modules) / total_pages) * 100)) if total_pages else 100
        session.state = state

        session.status = 'completed'
        session.completed_at = timezone.now()
        session.save(update_fields=['status', 'completed_at', 'state', 'updated_at'])
        return Response({'message': 'Session marked as completed', 'quiz_result': quiz_result})


class AgentInteractionLogViewSet(RequestParamMixin, ReadOnlyModelViewSet):
    queryset = AgentInteractionLog.objects.all()
    serializer_class = AgentInteractionLogSerializer
    permission_classes = [IsAuthenticated]
    lookup_field = 'uuid'

    def get_queryset(self):
        user = self.request.user
        manager_scope = Q()
        if user.is_manager:
            manager_scope = Q(session__role__organization__members=user)

        queryset = AgentInteractionLog.objects.filter(
            Q(session__user=user) |
            Q(session__role__organization__owner=user) |
            manager_scope
        ).distinct()

        if session_uuid := self._get_param('session_uuid'):
            queryset = queryset.filter(session__uuid=session_uuid)

        if role_uuid := self._get_param('role_uuid'):
            queryset = queryset.filter(session__role__uuid=role_uuid)

        if organization_uuid := self._get_param('organization_uuid'):
            queryset = queryset.filter(session__role__organization__uuid=organization_uuid)

        return queryset.order_by('created_at')
