import json
import ssl
import threading
import time
from urllib.parse import urlparse

import websocket
from flask import current_app, request
from flask_login import current_user

from src.config import FUNASR_WEBSOCKET_IP
from src.flask_ext import sock
from src.models import HotWord, YlHotWord


SCENE_YL = 'yl'
SCENE_CG = 'cg'


def _send_json(ws, payload):
    try:
        ws.send(json.dumps(payload, ensure_ascii=False))
    except Exception:
        pass


def _normalize_scene(scene):
    return SCENE_YL if str(scene or '').strip().lower() == SCENE_YL else SCENE_CG


def _hotword_model_for_scene(scene):
    return YlHotWord if _normalize_scene(scene) == SCENE_YL else HotWord


def _funasr_url():
    base = (FUNASR_WEBSOCKET_IP or '').strip().rstrip('/')
    if not base:
        raise RuntimeError('FUNASR_WEBSOCKET_IP 未配置')
    if not base.startswith(('ws://', 'wss://')):
        base = f'wss://{base}'
    parsed = urlparse(base)
    if parsed.path and parsed.path != '/':
        return base
    return f'{base}/websocket_offline'


def _hotwords_payload(logger, scene=SCENE_CG):
    try:
        hotword_model = _hotword_model_for_scene(scene)
        rows = hotword_model.query.order_by(hotword_model.created_at.desc()).all()
        return json.dumps({row.keyword: row.weight for row in rows}, ensure_ascii=False)
    except Exception as exc:
        logger.warning('加载实时 ASR 热词失败：%s', exc)
        return '{}'


def _asr_init_payload(mode, hotwords):
    return {
        'chunk_size': [10, 10, 10],
        'wav_name': 'h5',
        'is_speaking': True,
        'chunk_interval': 10,
        'itn': True,
        'mode': mode,
        'hotwords': hotwords,
    }


@sock.route('/ws/asr/live')
def live_asr_proxy(client_ws):
    if not current_user.is_authenticated:
        _send_json(client_ws, {'type': 'error', 'error': 'unauthorized'})
        return

    mode = request.args.get('mode') or 'online'
    if mode not in {'online', 'offline', '2pass'}:
        mode = 'online'
    scene = _normalize_scene(request.args.get('scene'))

    app = current_app._get_current_object()
    logger = app.logger
    hotwords = _hotwords_payload(logger, scene)

    upstream_open = threading.Event()
    upstream_closed = threading.Event()
    stop_requested = threading.Event()
    upstream_holder = {'ws': None}

    def on_open(upstream):
        upstream_holder['ws'] = upstream
        upstream.send(json.dumps(_asr_init_payload(mode, hotwords), ensure_ascii=False))
        upstream_open.set()
        _send_json(client_ws, {'type': 'state', 'state': 'connected', 'mode': mode, 'scene': scene})

    def on_message(_upstream, message):
        try:
            client_ws.send(message)
        except Exception:
            upstream_closed.set()

    def on_error(_upstream, error):
        logger.warning('FunASR WebSocket 错误：%s', error)
        _send_json(client_ws, {'type': 'error', 'error': 'funasr_error', 'message': str(error)})

    def on_close(_upstream, _status_code, _message):
        upstream_closed.set()
        _send_json(client_ws, {'type': 'state', 'state': 'closed'})

    try:
        upstream_url = _funasr_url()
    except Exception as exc:
        _send_json(client_ws, {'type': 'error', 'error': 'configuration_error', 'message': str(exc)})
        return

    upstream_app = websocket.WebSocketApp(
        upstream_url,
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close,
    )
    run_options = {}
    # FunASR 可能使用内网或自签名证书，仅对当前 WSS 上游连接跳过 TLS 证书和主机名校验。
    if upstream_url.startswith('wss://'):
        run_options['sslopt'] = {
            'cert_reqs': ssl.CERT_NONE,
            'check_hostname': False,
        }
    upstream_thread = threading.Thread(
        target=upstream_app.run_forever,
        kwargs=run_options,
        daemon=True,
    )
    upstream_thread.start()

    if not upstream_open.wait(timeout=10):
        _send_json(client_ws, {'type': 'error', 'error': 'funasr_unavailable', 'message': 'Timed out connecting to FunASR'})
        upstream_app.close()
        return

    try:
        while not upstream_closed.is_set():
            message = client_ws.receive(timeout=1)
            if message is None:
                continue
            upstream = upstream_holder.get('ws')
            if not upstream or not upstream.sock or not upstream.sock.connected:
                _send_json(client_ws, {'type': 'error', 'error': 'funasr_closed'})
                break
            if isinstance(message, bytes):
                upstream.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
                continue

            try:
                payload = json.loads(message)
            except (TypeError, ValueError):
                upstream.send(message)
                continue

            if payload.get('type') == 'stop':
                upstream.send(json.dumps({'is_speaking': False}, ensure_ascii=False))
                stop_requested.set()
                deadline = time.time() + 3.0
                while not upstream_closed.is_set() and time.time() < deadline:
                    time.sleep(0.05)
                break
            if 'is_speaking' in payload or payload.get('mode'):
                upstream.send(json.dumps(payload, ensure_ascii=False))
    except Exception as exc:
        logger.warning('Realtime ASR proxy session ended with error: %s', exc)
        _send_json(client_ws, {'type': 'error', 'error': 'proxy_error', 'message': str(exc)})
    finally:
        try:
            upstream_app.close()
        except Exception:
            pass
        upstream_closed.set()
        _send_json(client_ws, {'type': 'state', 'state': 'closed'})
        time.sleep(0.05)