# -*- coding: utf-8 -*-

import os
import fastapi
import socketio
import uvicorn
import logging
from dotenv import load_dotenv

# --- 伺服器設定 ---
HOST = "0.0.0.0"  # 監聽所有網路介面
PORT = 8765       # 與前端指定的埠號一致

# 設定日誌，方便追蹤連線與資料接收狀況
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# --- 全域狀態，用於處理晚加入的客戶端 ---
# 緩存 WebM 串流的標頭資料
webm_header = None
# 記錄廣播者的 SID
broadcaster_sid = None
# 定義房間名稱和聽眾資訊追蹤
LISTENERS_ROOM = 'listeners'
BROADCASTER_ROOM = 'broadcaster'
listeners_info = {} # 用於儲存聽眾的資訊，例如 {sid: {'ip': '1.2.3.4'}}

# 載入 .env 檔案中的環境變數
load_dotenv()

# 1. 建立 Socket.IO ASGI 應用
# 從環境變數讀取允許的來源，若未設定則預設為不允許任何來源，增加安全性
# 在 .env 檔案中設定: ALLOWED_ORIGINS=https://your-frontend-domain.com,http://localhost:3000
allowed_origins_str = os.getenv("ALLOWED_ORIGINS")

if allowed_origins_str == "*":
    cors_config = "*"
elif allowed_origins_str:
    cors_config = allowed_origins_str.split(",")
else:
    cors_config = [] # Default to no origins allowed for security

sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins=cors_config)

# Log the CORS configuration for easier debugging
if cors_config == "*":
    logger.info("CORS is configured to allow all origins.")
elif cors_config:
    logger.info(f"CORS is configured to allow the following origins: {', '.join(cors_config)}")
else:
    logger.warning("CORS is not configured. No cross-origin requests will be allowed.")
    logger.warning("To fix, set the ALLOWED_ORIGINS environment variable in your .env file (e.g., ALLOWED_ORIGINS=https://www.winway.tw:8765).")

# 2. 建立 FastAPI 應用
app = fastapi.FastAPI()

# 3. 將 Socket.IO 應用掛載到 FastAPI
# 這會讓 FastAPI 處理 HTTP 請求，而 Socket.IO 處理 WebSocket 請求
app.mount("/", socketio.ASGIApp(sio))

# 4. 定義 Socket.IO 事件處理器
@sio.event
async def connect(sid, environ):
    """當客戶端連接時觸發"""
    global webm_header, broadcaster_sid, listeners_info

    # 從 environ 取得客戶端 IP 位址，優先使用 X-Forwarded-For (若有反向代理)
    ip_address = environ.get('REMOTE_ADDR', 'Unknown IP')
    if 'HTTP_X_FORWARDED_FOR' in environ:
        ip_address = environ['HTTP_X_FORWARDED_FOR'].split(',')[0].strip()

    logger.info(f"Client connected: {sid} from IP: {ip_address}")

    # 儲存聽眾資訊
    listeners_info[sid] = {'ip': ip_address}

    # If a broadcast is already in progress, send the header and wait for acknowledgment.
    # This prevents a race condition where a regular audio chunk could arrive at the
    # client before the header.
    if webm_header:
        logger.info(f"Active broadcast detected. Sending WebM header to {sid}")
        try:
            # This will wait for the client handler to return a value (the acknowledgment)
            await sio.emit('audio_broadcast', webm_header, to=sid)
        except Exception as e:
            logger.error(f"Failed to send header or get ack from {sid}: {e}")
            # It's safer to disconnect a client that fails this handshake.
            if sid in listeners_info:
                del listeners_info[sid]
            await sio.disconnect(sid)
            return

    # 將客戶端加入聽眾房間
    await sio.enter_room(sid, LISTENERS_ROOM)
    logger.info(f"Client {sid} has joined the '{LISTENERS_ROOM}' room.")

    # 通知廣播者有新的聽眾加入
    if broadcaster_sid:
        await sio.emit('listener_joined', {'sid': sid, 'ip': ip_address}, room=BROADCASTER_ROOM)

@sio.on('start_broadcast')
async def start_broadcast(sid, data):
    """A client requests to become the broadcaster."""
    global broadcaster_sid, webm_header, listeners_info
    if broadcaster_sid is not None and broadcaster_sid != sid:
        logger.warning(f"Client {sid} tried to start a broadcast, but {broadcaster_sid} is already broadcasting.")
        await sio.emit('broadcast_error', {'message': 'Another broadcast is already in progress.'}, to=sid)
        return

    logger.info(f"Broadcast session starting for client {sid}.")
    broadcaster_sid = sid
    webm_header = None  # Reset header for the new stream
    # 廣播者不應在聽眾房間 (避免聽到自己的回音)，並加入廣播者房間
    await sio.leave_room(sid, LISTENERS_ROOM)
    await sio.enter_room(sid, BROADCASTER_ROOM)

    # Acknowledge to the client that they can start sending audio.
    await sio.emit('broadcast_started', to=sid)

    # 將目前的聽眾列表傳送給新的廣播者
    # 排除廣播者自己
    current_listeners = {s: info for s, info in listeners_info.items() if s != sid}
    await sio.emit('current_listeners', current_listeners, to=sid)

@sio.on('audio')
async def handle_audio(sid, data):
    """接收從前端傳來的 'audio' 事件，並廣播給其他客戶端"""
    global webm_header, broadcaster_sid

    # Only accept audio from the designated broadcaster.
    if sid != broadcaster_sid:
        logger.debug(f"Received audio from non-broadcaster {sid}. Ignoring.")
        return

    # Ignore empty data packets.
    if not data:
        return

    # The first data chunk is cached as the header.
    if not webm_header:
        logger.info(f"Caching WebM header for broadcast from {sid}.")
        webm_header = data

    # Broadcast the audio data to all listeners.
    await sio.emit('audio_broadcast', data, room=LISTENERS_ROOM)

@sio.event
async def disconnect(sid):
    """當客戶端斷開連接時觸發"""
    global webm_header, broadcaster_sid, listeners_info

    # 從追蹤列表中移除
    if sid in listeners_info:
        del listeners_info[sid]

    # 如果是廣播者斷線，重設整個串流狀態
    if sid == broadcaster_sid:
        logger.info(f"Broadcaster {sid} has disconnected. Clearing stream state.")
        webm_header = None
        broadcaster_sid = None
        # 通知所有還在線的聽眾，廣播已結束
        await sio.emit('broadcast_ended', room=LISTENERS_ROOM, skip_sid=sid)
    else:
        # 如果是聽眾斷線，通知廣播者
        await sio.emit('listener_left', {'sid': sid}, room=BROADCASTER_ROOM)
    logger.info(f"Client disconnected: {sid}")

if __name__ == '__main__':
    # 使用 uvicorn 運行伺服器
    # 注意：前端使用 wss://，所以後端需要啟用 SSL。
    # --- SSL/TLS 設定 ---
    # 從 .env 檔案讀取 SSL 路徑
    ssl_keyfile = os.getenv("SSL_KEYFILE")
    ssl_certfile = os.getenv("SSL_CERTFILE")

    # 啟動前檢查，確保 SSL 憑證設定正確
    if not all([ssl_keyfile, ssl_certfile]):
        logger.error("錯誤: SSL_KEYFILE 或 SSL_CERTFILE 環境變數未在 .env 檔案中設定。")
        exit(1)

    if not os.path.exists(ssl_keyfile) or not os.path.exists(ssl_certfile):
        logger.error("錯誤: SSL 憑證檔案或私鑰檔案不存在，請檢查 .env 中的路徑。")
        logger.error(f"  - 檢查私鑰路徑: {ssl_keyfile}")
        logger.error(f"  - 檢查憑證路徑: {ssl_certfile}")
        exit(1)

    uvicorn.run(
        app,
        host=HOST,
        port=PORT,
        ssl_keyfile=ssl_keyfile, # 如果為 None，uvicorn 會以 http 模式運行
        ssl_certfile=ssl_certfile # 如果為 None，uvicorn 會以 http 模式運行
    ) 