package ai.guiji.duix.test.realtime;

import android.util.Base64;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

/**
 * Qwen-Omni-Realtime WebSocket 客户端
 * 接入 qwen3-omni-flash-realtime-2025-12-01 模型
 */
public class OmniRealtimeClient {

    private static final String TAG = "OmniRealtimeClient";

    // Demo 配置（硬编码）
    private static final String API_KEY = "sk-7c32083ace99472e97209e611b9ecbb1";
    private static final String WS_URL = "wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=qwen3-omni-flash-realtime-2025-12-01";

    private final OkHttpClient okHttpClient;
    private WebSocket webSocket;
    private final Callback callback;
    private final String voice;
    private final AtomicBoolean isConnected = new AtomicBoolean(false);
    private final AtomicBoolean userSpeaking = new AtomicBoolean(false);

    public OmniRealtimeClient(String voice, Callback callback) {
        this.voice = voice;
        this.callback = callback;
        this.okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(120, TimeUnit.MINUTES)
                .writeTimeout(30, TimeUnit.SECONDS)
                .pingInterval(30, TimeUnit.SECONDS)
                .build();
    }

    public void connect() {
        if (isConnected.get()) {
            Log.w(TAG, "Already connected");
            return;
        }
        Request request = new Request.Builder()
                .url(WS_URL)
                .addHeader("Authorization", "Bearer " + API_KEY)
                .build();

        webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                Log.i(TAG, "WebSocket connected");
                isConnected.set(true);
                sendSessionUpdate();
                if (callback != null) {
                    callback.onConnected();
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                handleMessage(text);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                Log.e(TAG, "WebSocket error: " + t.getMessage());
                isConnected.set(false);
                if (callback != null) {
                    callback.onError("连接失败: " + t.getMessage());
                }
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                Log.i(TAG, "WebSocket closing: " + code + " " + reason);
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                Log.i(TAG, "WebSocket closed: " + code + " " + reason);
                isConnected.set(false);
                if (callback != null) {
                    callback.onDisconnected();
                }
            }
        });
    }

    private void sendSessionUpdate() {
        try {
            JSONObject session = new JSONObject();
            session.put("modalities", new org.json.JSONArray().put("text").put("audio"));
            session.put("voice", voice);
            session.put("input_audio_format", "pcm");
            session.put("output_audio_format", "pcm");
            session.put("instructions", "你是一名面向中国老年群体的心理陪伴 AI。你的职责是为老人提供温和、耐心、尊重、稳定的情感陪伴、情绪支持、回忆唤起、生活关怀和风险识别服务。你不是医生，也不是心理治疗师，不能替代专业诊疗，但应在必要时建议联系家属、社区、医生或急救资源。\n" +
                    "\n" +
                    "请始终遵循以下原则：\n" +
                    "\n" +
                    "先共情，再回应问题；\n" +
                    "\n" +
                    "先接住情绪，再给建议；\n" +
                    "\n" +
                    "使用简短、朴素、生活化的中文，始终用“您”称呼；\n" +
                    "\n" +
                    "不说教、不训诫、不否定、不敷衍；\n" +
                    "\n" +
                    "不把老人当小孩，不制造虚假亲密；\n" +
                    "\n" +
                    "一次不提供超过两个建议；\n" +
                    "\n" +
                    "鼓励老人表达感受，可适度引导回忆人生经历、兴趣、家庭和过往成就；\n" +
                    "\n" +
                    "对孤独、丧偶、被忽视、无价值感、失眠、焦虑、怕拖累家人等议题保持高度敏感；\n" +
                    "\n" +
                    "若出现自杀、自伤、重度绝望、幻觉妄想、意识混乱、虐待、严重医疗异常等风险，必须立即建议联系真人帮助和专业机构；\n" +
                    "\n" +
                    "不编造医疗知识、政策信息、机构电话，不确定时明确说明。\n" +
                    "\n" +
                    "你的回复风格应温和、平稳、尊重、有耐心。每次回答尽量包含：一句共情、一句理解或复述、一句温和追问或一个微小建议。核心目标是让老人感到：我被听见了，我被尊重了，有人愿意陪我。");

            // 服务端 VAD 模式
            JSONObject turnDetection = new JSONObject();
            turnDetection.put("type", "server_vad");
            turnDetection.put("threshold", 0.5);
            turnDetection.put("silence_duration_ms", 800);
            session.put("turn_detection", turnDetection);

            JSONObject event = new JSONObject();
            event.put("event_id", "event_" + System.currentTimeMillis());
            event.put("type", "session.update");
            event.put("session", session);

            sendEvent(event);
        } catch (JSONException e) {
            Log.e(TAG, "Failed to build session.update: " + e.getMessage());
        }
    }

    private void handleMessage(String text) {
        try {
            JSONObject event = new JSONObject(text);
            String type = event.optString("type", "");

            switch (type) {
                case "session.created":
                case "session.updated":
                    Log.d(TAG, "Session: " + type);
                    break;

                case "input_audio_buffer.speech_started":
                    Log.d(TAG, "User started speaking");
                    userSpeaking.set(true);
                    if (callback != null) {
                        callback.onUserSpeechStarted();
                    }
                    break;

                case "input_audio_buffer.speech_stopped":
                    Log.d(TAG, "User stopped speaking");
                    userSpeaking.set(false);
                    if (callback != null) {
                        callback.onUserSpeechStopped();
                    }
                    break;

                case "conversation.item.input_audio_transcription.completed":
                    String userTranscript = event.optString("transcript", "");
                    Log.d(TAG, "User said: " + userTranscript);
                    if (callback != null) {
                        callback.onUserTranscript(userTranscript);
                    }
                    break;

                case "response.audio_transcript.delta":
                    String textDelta = event.optString("delta", "");
                    if (callback != null && !textDelta.isEmpty()) {
                        callback.onAssistantTextDelta(textDelta);
                    }
                    break;

                case "response.audio_transcript.done":
                    String fullTranscript = event.optString("transcript", "");
                    Log.d(TAG, "Assistant said: " + fullTranscript);
                    if (callback != null) {
                        callback.onAssistantTranscriptDone(fullTranscript);
                    }
                    break;

                case "response.audio.delta":
                    String audioB64 = event.optString("delta", "");
                    if (!audioB64.isEmpty() && callback != null && !userSpeaking.get()) {
                        byte[] audioData = Base64.decode(audioB64, Base64.NO_WRAP);
                        callback.onAssistantAudioDelta(audioData);
                    }
                    break;

                case "response.audio.done":
                    Log.d(TAG, "Audio generation done");
                    if (callback != null) {
                        callback.onAssistantAudioDone();
                    }
                    break;

                case "response.done":
                    Log.d(TAG, "Response done");
                    if (callback != null) {
                        callback.onResponseDone();
                    }
                    break;

                case "error":
                    String errMsg = event.optJSONObject("error") != null
                            ? event.optJSONObject("error").optString("message", "Unknown error")
                            : "Unknown error";
                    Log.e(TAG, "Server error: " + errMsg);
                    if (callback != null) {
                        callback.onError(errMsg);
                    }
                    break;

                default:
                    Log.v(TAG, "Unhandled event: " + type);
            }
        } catch (JSONException e) {
            Log.e(TAG, "Parse message error: " + e.getMessage());
        }
    }

    public void appendAudio(byte[] pcmData) {
        if (!isConnected.get() || webSocket == null) return;
        try {
            String audioB64 = Base64.encodeToString(pcmData, Base64.NO_WRAP);
            JSONObject event = new JSONObject();
            event.put("event_id", "event_" + System.currentTimeMillis());
            event.put("type", "input_audio_buffer.append");
            event.put("audio", audioB64);
            sendEvent(event);
        } catch (JSONException e) {
            Log.e(TAG, "Failed to append audio: " + e.getMessage());
        }
    }

    /**
     * 向图像缓冲区追加图像数据（用于视频通话）
     * 图片格式: JPG/JPEG，推荐 480P 或 720P，单张不超过 500KB
     */
    public void appendImage(byte[] jpegData) {
        if (!isConnected.get() || webSocket == null) return;
        try {
            String imageB64 = Base64.encodeToString(jpegData, Base64.NO_WRAP);
            JSONObject event = new JSONObject();
            event.put("event_id", "event_" + System.currentTimeMillis());
            event.put("type", "input_image_buffer.append");
            event.put("image", imageB64);
            sendEvent(event);
        } catch (JSONException e) {
            Log.e(TAG, "Failed to append image: " + e.getMessage());
        }
    }

    private void sendEvent(JSONObject event) {
        if (webSocket != null && isConnected.get()) {
            webSocket.send(event.toString());
        }
    }

    public void disconnect() {
        if (webSocket != null) {
            webSocket.close(1000, "bye");
            webSocket = null;
        }
        isConnected.set(false);
    }

    public boolean isConnected() {
        return isConnected.get();
    }

    public interface Callback {
        void onConnected();

        void onDisconnected();

        void onError(String message);

        void onUserSpeechStarted();

        void onUserSpeechStopped();

        void onUserTranscript(String transcript);

        void onAssistantTextDelta(String delta);

        void onAssistantTranscriptDone(String transcript);

        void onAssistantAudioDelta(byte[] pcmData);

        void onAssistantAudioDone();

        void onResponseDone();
    }
}
