"""Survey manager: tracks events, communicates with Space to fetch/submit surveys."""

from __future__ import annotations

import contextlib
import json
import typing
import httpx
import sqlalchemy

from ..core import app as core_app
from ..core import entities as core_entities
from ..entity.persistence.metadata import Metadata
from ..persistence.tenant_uow import CrossScopeTransactionError
from ..utils import constants, httpclient

SURVEY_TRIGGERED_KEY = 'survey_triggered_events'
BOT_RESPONSE_COUNT_KEY = 'survey_bot_response_count'

# Milestone event fired when an instance accumulates this many successful bot responses
BOT_RESPONSE_MILESTONE = 100
BOT_RESPONSE_MILESTONE_EVENT = f'bot_response_success_{BOT_RESPONSE_MILESTONE}'


class SurveyManager:
    """Manages survey lifecycle: event tracking, pending survey fetch, submission."""

    def __init__(self, ap: core_app.Application):
        self.ap = ap
        self._triggered_events: set[str] = set()
        self._pending_survey: typing.Optional[dict] = None
        self._space_url: str = ''
        self._bot_response_count: int = 0

    async def initialize(self):
        space_config = self.ap.instance_config.data.get('space', {})
        self._space_url = space_config.get('url', '').rstrip('/')
        await self._load_triggered_events()
        await self._load_bot_response_count()

    @contextlib.asynccontextmanager
    async def _instance_transaction(self):
        """Bind instance-global survey metadata to an explicit Cloud transaction."""

        persistence_mgr = self.ap.persistence_mgr
        try:
            active_session = getattr(persistence_mgr, 'current_session', lambda: None)()
        except CrossScopeTransactionError:
            # A newly-created child task inherited its parent's ContextVar;
            # opening an explicit UoW below gives it an independent session.
            active_session = None
        if active_session is not None:
            yield
            return

        cloud_runtime = getattr(getattr(persistence_mgr, 'mode', None), 'value', None) == 'cloud_runtime'
        if cloud_runtime:
            instance_uow = getattr(persistence_mgr, 'instance_discovery_uow', None)
            if not callable(instance_uow):
                raise RuntimeError('Cloud survey metadata requires an explicit instance UoW')
            async with instance_uow(self.ap.workspace_service.instance_uuid):
                yield
            return
        yield

    async def _load_triggered_events(self):
        """Load previously triggered events from metadata table."""
        try:
            async with self._instance_transaction():
                result = await self.ap.persistence_mgr.execute_async(
                    sqlalchemy.select(Metadata.value).where(Metadata.key == SURVEY_TRIGGERED_KEY)
                )
                value = result.scalar_one_or_none()
                if value is not None:
                    self._triggered_events = set(json.loads(value))
        except Exception:
            self._triggered_events = set()

    async def _save_triggered_events(self):
        """Persist triggered events to metadata table."""
        try:
            value = json.dumps(list(self._triggered_events))
            async with self._instance_transaction():
                result = await self.ap.persistence_mgr.execute_async(
                    sqlalchemy.select(Metadata.value).where(Metadata.key == SURVEY_TRIGGERED_KEY)
                )
                if result.scalar_one_or_none() is not None:
                    await self.ap.persistence_mgr.execute_async(
                        sqlalchemy.update(Metadata).where(Metadata.key == SURVEY_TRIGGERED_KEY).values(value=value)
                    )
                else:
                    await self.ap.persistence_mgr.execute_async(
                        sqlalchemy.insert(Metadata).values(key=SURVEY_TRIGGERED_KEY, value=value)
                    )
        except Exception as e:
            self.ap.logger.debug(f'Failed to save survey triggered events: {e}')

    def _is_space_configured(self) -> bool:
        space_config = self.ap.instance_config.data.get('space', {})
        if space_config.get('disable_telemetry', False):
            return False
        return bool(self._space_url)

    async def _load_bot_response_count(self):
        """Load the persisted successful bot response count from metadata table."""
        try:
            async with self._instance_transaction():
                result = await self.ap.persistence_mgr.execute_async(
                    sqlalchemy.select(Metadata.value).where(Metadata.key == BOT_RESPONSE_COUNT_KEY)
                )
                value = result.scalar_one_or_none()
                if value is not None:
                    self._bot_response_count = int(value)
        except Exception:
            self._bot_response_count = 0

    async def _save_bot_response_count(self):
        """Persist the successful bot response count to metadata table."""
        try:
            value = str(self._bot_response_count)
            async with self._instance_transaction():
                result = await self.ap.persistence_mgr.execute_async(
                    sqlalchemy.select(Metadata.value).where(Metadata.key == BOT_RESPONSE_COUNT_KEY)
                )
                if result.scalar_one_or_none() is not None:
                    await self.ap.persistence_mgr.execute_async(
                        sqlalchemy.update(Metadata).where(Metadata.key == BOT_RESPONSE_COUNT_KEY).values(value=value)
                    )
                else:
                    await self.ap.persistence_mgr.execute_async(
                        sqlalchemy.insert(Metadata).values(key=BOT_RESPONSE_COUNT_KEY, value=value)
                    )
        except Exception as e:
            self.ap.logger.debug(f'Failed to save survey bot response count: {e}')

    async def record_bot_response_success(self):
        """Count a successful bot response; fires the milestone event at the threshold.

        Called by the chat handler after each successful (non-WebSocket) response.
        The count is persisted so it survives restarts. Once the milestone event
        has been triggered, counting stops (no further writes needed).
        """
        if BOT_RESPONSE_MILESTONE_EVENT in self._triggered_events:
            return
        if not self._is_space_configured():
            return

        self._bot_response_count += 1
        await self._save_bot_response_count()

        if self._bot_response_count >= BOT_RESPONSE_MILESTONE:
            await self.trigger_event(BOT_RESPONSE_MILESTONE_EVENT)

    async def trigger_event(self, event: str):
        """Called when an event occurs. Checks Space for a pending survey."""
        if event in self._triggered_events:
            return
        if not self._is_space_configured():
            return

        self._triggered_events.add(event)
        await self._save_triggered_events()

        # Check for pending survey asynchronously
        self.ap.task_mgr.create_task(
            self._fetch_pending_survey(event),
            kind='survey-fetch',
            name=f'survey-fetch-{event}',
            scopes=[core_entities.LifecycleControlScope.APPLICATION],
            instance_uuid=self.ap.workspace_service.instance_uuid,
        )

    async def _fetch_pending_survey(self, event: str):
        """Fetch pending survey from Space for this event."""
        try:
            url = f'{self._space_url}/api/v1/survey/pending'
            payload = {
                'instance_id': constants.instance_id,
                'event': event,
            }
            async with httpx.AsyncClient(
                timeout=httpx.Timeout(10),
                event_hooks=httpclient.httpx_response_limit_hooks(),
            ) as client:
                resp = await client.post(url, json=payload)
                if resp.status_code == 200:
                    data = await httpclient.parse_json_response(resp)
                    if data.get('code') == 0 and data.get('data', {}).get('survey'):
                        self._pending_survey = data['data']['survey']
                        self.ap.logger.info(f'Survey pending: {self._pending_survey.get("survey_id")}')
        except Exception as e:
            self.ap.logger.debug(f'Failed to fetch pending survey: {e}')

    def get_pending_survey(self) -> typing.Optional[dict]:
        """Return the current pending survey (if any) for the frontend to display."""
        return self._pending_survey

    def clear_pending_survey(self):
        """Clear the pending survey (after user responds or dismisses)."""
        self._pending_survey = None

    async def _build_base_metadata(self, user_email: str | None = None) -> dict:
        metadata = {
            'version': constants.semantic_version,
            'instance_id': constants.instance_id,
        }
        if user_email:
            metadata['login_account'] = user_email
            try:
                user_obj = await self.ap.user_service.get_user_by_email(user_email)
                metadata['account_type'] = getattr(user_obj, 'account_type', '') or 'local'
                metadata['space_account_uuid'] = getattr(user_obj, 'space_account_uuid', '') or ''
            except Exception:
                pass
        return metadata

    async def submit_response(self, survey_id: str, answers: dict, completed: bool = True) -> bool:
        """Submit a survey response to Space."""
        if not self._is_space_configured():
            return False
        try:
            url = f'{self._space_url}/api/v1/survey/respond'
            payload = {
                'survey_id': survey_id,
                'instance_id': constants.instance_id,
                'answers': answers,
                'metadata': await self._build_base_metadata(),
                'completed': completed,
            }
            async with httpx.AsyncClient(
                timeout=httpx.Timeout(10),
                event_hooks=httpclient.httpx_response_limit_hooks(),
            ) as client:
                resp = await client.post(url, json=payload)
                if resp.status_code == 200:
                    self.clear_pending_survey()
                    return True
        except Exception as e:
            self.ap.logger.warning(f'Failed to submit survey response: {e}')
        return False

    async def submit_feedback(
        self,
        content: str,
        attachments: list[dict],
        user_email: str | None = None,
    ) -> bool:
        """Submit an on-demand user feedback item to Space."""
        if not self._is_space_configured():
            return False
        try:
            url = f'{self._space_url}/api/v1/survey/feedback'
            metadata = await self._build_base_metadata(user_email)
            payload = {
                'instance_id': constants.instance_id,
                'content': content,
                'attachments': attachments,
                'metadata': metadata,
            }
            async with httpx.AsyncClient(
                timeout=httpx.Timeout(30),
                event_hooks=httpclient.httpx_response_limit_hooks(),
            ) as client:
                resp = await client.post(url, json=payload)
                if resp.status_code == 200:
                    return True
                body = await httpclient.response_text(resp, max_chars=200)
                self.ap.logger.warning(f'Failed to submit feedback: {resp.status_code} {body}')
        except Exception as e:
            self.ap.logger.warning(f'Failed to submit feedback: {e}')
        return False

    async def dismiss_survey(self, survey_id: str) -> bool:
        """Dismiss a survey."""
        if not self._is_space_configured():
            return False
        try:
            url = f'{self._space_url}/api/v1/survey/dismiss'
            payload = {
                'survey_id': survey_id,
                'instance_id': constants.instance_id,
            }
            async with httpx.AsyncClient(
                timeout=httpx.Timeout(10),
                event_hooks=httpclient.httpx_response_limit_hooks(),
            ) as client:
                resp = await client.post(url, json=payload)
                if resp.status_code == 200:
                    self.clear_pending_survey()
                    return True
        except Exception as e:
            self.ap.logger.warning(f'Failed to dismiss survey: {e}')
        return False
