# -*- coding: utf-8 -*-
"""
ACP Group Client - Python implementation of group protocol.
Sends JSON requests via ACP message transport to group.{ap} server.
"""
import json
import threading
import time
import urllib.parse

from agentcp.base.log import log_info, log_error, log_warning


def send_raw_to_session(agent_id_obj, session_id, target_aid, raw_payload):
    """Send raw JSON payload via ACP session without content block wrapping."""
    session = agent_id_obj.session_manager.get(session_id)
    if not session or not session.message_client:
        raise Exception('Session not found or not connected')
    encoded = urllib.parse.quote(raw_payload)
    data = {
        "cmd": "session_message",
        "data": {
            "message_id": str(int(time.time() * 1000)),
            "session_id": session_id,
            "ref_msg_id": "",
            "sender": agent_id_obj.id,
            "instruction": None,
            "receiver": target_aid,
            "message": encoded,
            "timestamp": str(int(time.time() * 1000)),
        }
    }
    return session.message_client.send_msg(json.dumps(data))


class GroupClient:
    """Core request/response transport for ACP group protocol."""

    def __init__(self, agent_id_obj, session_id, target_aid):
        self._agent_id = agent_id_obj
        self._session_id = session_id
        self._target_aid = target_aid
        self._pending = {}  # request_id -> {event, response}
        self._seq = 0
        self._timeout = 30
        self._lock = threading.Lock()
        self._on_message_push = None  # callback(group_id, message_dict)

    def set_on_message_push(self, callback):
        """Set callback for incoming group message pushes: callback(group_id, message)"""
        self._on_message_push = callback

    def _next_id(self):
        self._seq += 1
        return f"{self._agent_id.id}-{int(time.time()*1000)}-{self._seq}"

    def send_request(self, action, group_id='', params=None):
        req_id = self._next_id()
        req = {'action': action, 'request_id': req_id}
        if group_id:
            req['group_id'] = group_id
        if params is not None:
            req['params'] = params

        event = threading.Event()
        with self._lock:
            self._pending[req_id] = {'event': event, 'response': None}

        payload = json.dumps(req)
        try:
            send_raw_to_session(self._agent_id, self._session_id, self._target_aid, payload)
        except Exception as e:
            with self._lock:
                self._pending.pop(req_id, None)
            raise

        if not event.wait(timeout=self._timeout):
            with self._lock:
                self._pending.pop(req_id, None)
            raise TimeoutError(f'Group request timeout: {action}')

        with self._lock:
            entry = self._pending.pop(req_id, None)
        resp = entry['response'] if entry else None
        if not resp:
            raise Exception(f'No response for {action}')
        if resp.get('code', -1) != 0:
            raise Exception(resp.get('error', f'{action} failed: code={resp.get("code")}'))
        return resp

    def handle_incoming(self, payload_str):
        """Try to handle as group protocol message. Returns True if handled."""
        try:
            data = json.loads(payload_str) if isinstance(payload_str, str) else payload_str
        except Exception:
            return False
        if not isinstance(data, dict):
            return False

        req_id = data.get('request_id', '')
        if req_id:
            with self._lock:
                entry = self._pending.get(req_id)
            if entry:
                entry['response'] = data
                entry['event'].set()
                return True

        action = data.get('action', '')
        event = data.get('event', '')
        if action in ('message_push', 'message_batch_push') or event:
            if self._on_message_push:
                try:
                    d = data.get('data', {})
                    if action == 'message_batch_push':
                        for msg in d.get('messages', []):
                            gid = msg.get('group_id', '') or d.get('group_id', '')
                            self._on_message_push(gid, msg)
                    else:
                        gid = d.get('group_id', '')
                        self._on_message_push(gid, d)
                except Exception as e:
                    log_warning(f'[GroupClient] message_push callback error: {e}')
            return True

        return False


class GroupOperations:
    """High-level group operations wrapping GroupClient."""

    def __init__(self, client: GroupClient, target_aid: str):
        self._client = client
        self._target = target_aid

    def _req(self, action, group_id='', params=None):
        return self._client.send_request(action, group_id, params)

    def register_online(self):
        self._req('register_online')

    def create_group(self, name, visibility='', description=''):
        params = {'name': name}
        if visibility:
            params['visibility'] = visibility
        if description:
            params['description'] = description
        resp = self._req('create_group', '', params)
        d = resp.get('data', {})
        return {'group_id': d.get('group_id', ''), 'group_url': d.get('group_url', '')}

    def send_message(self, group_id, content, content_type='text'):
        params = {'content': content, 'content_type': content_type}
        resp = self._req('send_message', group_id, params)
        d = resp.get('data', {})
        return {'msg_id': d.get('msg_id', 0), 'timestamp': d.get('timestamp', 0)}

    def pull_messages(self, group_id, after_msg_id=0, limit=0):
        params = {}
        if after_msg_id > 0:
            params['after_msg_id'] = after_msg_id
        if limit > 0:
            params['limit'] = limit
        resp = self._req('pull_messages', group_id, params or None)
        d = resp.get('data', {})
        return {
            'messages': d.get('messages', []),
            'has_more': d.get('has_more', False),
            'latest_msg_id': d.get('latest_msg_id', 0),
        }

    def get_group_info(self, group_id):
        resp = self._req('get_group_info', group_id)
        return resp.get('data', {})

    def get_members(self, group_id):
        resp = self._req('get_members', group_id)
        d = resp.get('data', {})
        return {'members': d.get('members', [])}

    def request_join(self, group_id, message=''):
        params = {'message': message} if message else None
        resp = self._req('request_join', group_id, params)
        d = resp.get('data', {})
        return {'status': d.get('status', 'pending'), 'request_id': d.get('request_id', '')}

    def use_invite_code(self, group_id, code):
        self._req('use_invite_code', group_id, {'code': code})

    def leave_group(self, group_id):
        self._req('leave_group', group_id)

    def create_invite_code(self, group_id, options=None):
        resp = self._req('create_invite_code', group_id, options)
        return resp.get('data', {})

    def list_my_groups(self, status=0):
        params = {'status': status} if status else None
        resp = self._req('list_my_groups', '', params)
        d = resp.get('data', {})
        return {'groups': d.get('groups', []), 'total': d.get('total', 0)}

    def review_join_request(self, group_id, agent_id, action, reason=''):
        params = {'agent_id': agent_id, 'action': action}
        if reason:
            params['reason'] = reason
        self._req('review_join_request', group_id, params)

    def get_pending_requests(self, group_id):
        resp = self._req('get_pending_requests', group_id)
        d = resp.get('data', {})
        return {'requests': d.get('requests', [])}

    def update_duty_config(self, group_id, config):
        self._req('update_duty_config', group_id, {'duty_config': config})

    def get_duty_status(self, group_id):
        resp = self._req('get_duty_status', group_id)
        d = resp.get('data', {})
        return {'config': d.get('config', {}), 'state': d.get('state', {})}

    @staticmethod
    def parse_group_url(group_url):
        from urllib.parse import urlparse
        parsed = urlparse(group_url)
        target_aid = parsed.hostname or ''
        group_id = parsed.path.lstrip('/')
        if not target_aid or not group_id:
            raise Exception(f'Invalid group URL: {group_url}')
        return target_aid, group_id
