use super::AppState;
use axum::{
    extract::{Path, State, WebSocketUpgrade, ws::{Message, WebSocket}},
    http::{HeaderMap, StatusCode, header},
    response::IntoResponse,
};
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use zeroclaw_channels::weixin::{QrPollStatus, WeixinChannel};

const BEARER_SUBPROTO_PREFIX: &str = "bearer.";
const WS_BIND_PROTOCOL: &str = "zeroclaw.bind.v1";

const WEIXIN_QR_EXPIRY_SECS: u64 = 300;
const WEIXIN_POLL_INTERVAL_SECS: u64 = 2;
const WEIXIN_POLL_TIMEOUT_SECS: u64 = 300;

#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum BindInboundMessage {
    Start,
    Refresh,
}

#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum BindOutboundMessage {
    QrCode {
        channel: String,
        image_base64: String,
        expires_at: u64,
    },
    QrExpired {
        channel: String,
    },
    Scaned {
        channel: String,
    },
    BindSuccess {
        channel: String,
        account_id: Option<String>,
        user_id: Option<String>,
    },
    Error {
        channel: String,
        message: String,
    },
    Connected {
        channel: String,
    },
}

#[derive(Debug)]
enum PollEvent {
    Scaned,
    Confirmed {
        account_id: Option<String>,
        user_id: Option<String>,
    },
    Expired,
}

fn extract_ws_token<'a>(headers: &'a HeaderMap, query_token: Option<&'a str>) -> Option<&'a str> {
    if let Some(t) = headers
        .get(header::AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .and_then(|auth| auth.strip_prefix("Bearer "))
        && !t.is_empty()
    {
        return Some(t);
    }

    if let Some(t) = headers
        .get("sec-websocket-protocol")
        .and_then(|v| v.to_str().ok())
        .and_then(|protos| {
            protos
                .split(',')
                .map(|p| p.trim())
                .find_map(|p| p.strip_prefix(BEARER_SUBPROTO_PREFIX))
        })
        && !t.is_empty()
    {
        return Some(t);
    }

    if let Some(t) = query_token
        && !t.is_empty()
    {
        return Some(t);
    }

    None
}

fn now_epoch_secs() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

pub async fn handle_ws_bind(
    State(state): State<AppState>,
    Path(channel): Path<String>,
    headers: HeaderMap,
    ws: WebSocketUpgrade,
) -> impl IntoResponse {
    let is_bind_channel = matches!(channel.to_lowercase().as_str(), "weixin");

    if state.pairing.require_pairing() && !is_bind_channel {
        let token = extract_ws_token(&headers, None).unwrap_or("");
        if !state.pairing.is_authenticated(token) {
            return (
                StatusCode::UNAUTHORIZED,
                "Unauthorized — provide Authorization header or Sec-WebSocket-Protocol bearer",
            )
            .into_response();
        }
    }

    let ws = if headers
        .get("sec-websocket-protocol")
        .and_then(|v| v.to_str().ok())
        .is_some_and(|protos| protos.split(',').any(|p| p.trim() == WS_BIND_PROTOCOL))
    {
        ws.protocols([WS_BIND_PROTOCOL])
    } else {
        ws
    };

    let config = state.config.lock().clone();

    ws.on_upgrade(move |socket| handle_bind_socket(socket, channel, config))
        .into_response()
}

pub async fn handle_bind_page(
    Path(channel): Path<String>,
) -> impl IntoResponse {
    let channel_name = channel.to_lowercase();
    match channel_name.as_str() {
        "weixin" => serve_bind_html().await,
        _ => (
            StatusCode::NOT_FOUND,
            format!("Bind page not available for channel: {channel}"),
        )
            .into_response(),
    }
}

const WEIXIN_BIND_HTML: &str = include_str!("../../../web/public/bind/weixin.html");
const WEIXIN_ICON_SVG: &[u8] = include_bytes!("../../../web/public/icons/weixin.svg");

async fn serve_bind_html() -> axum::response::Response {
    (
        StatusCode::OK,
        [(header::CONTENT_TYPE, "text/html; charset=utf-8".to_string())],
        WEIXIN_BIND_HTML,
    )
        .into_response()
}

pub async fn handle_icon(
    Path(path): Path<String>,
) -> impl IntoResponse {
    if path.contains("..") {
        return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
    }

    if path == "weixin.svg" {
        return (
            StatusCode::OK,
            [
                (header::CONTENT_TYPE, "image/svg+xml".to_string()),
                (header::CACHE_CONTROL, "public, max-age=86400".to_string()),
            ],
            WEIXIN_ICON_SVG,
        )
            .into_response();
    }

    (StatusCode::NOT_FOUND, "Icon not found").into_response()
}

async fn handle_bind_socket(
    socket: WebSocket,
    channel: String,
    config: zeroclaw_config::schema::Config,
) {
    let (mut sender, mut receiver) = socket.split();

    let channel_name = channel.to_lowercase();

    match channel_name.as_str() {
        "weixin" => {}
        _ => {
            let msg = BindOutboundMessage::Error {
                channel: channel.clone(),
                message: format!("Unsupported bind channel: {channel}"),
            };
            let _ = send_json(&mut sender, &msg).await;
            return;
        }
    }

    let connected_msg = BindOutboundMessage::Connected {
        channel: channel_name.clone(),
    };
    if send_json(&mut sender, &connected_msg).await.is_err() {
        return;
    }

    let mut active_qr_key: Option<String> = None;
    let mut active_qr_expires: Option<u64> = None;
    let mut poll_cancel: Option<tokio_util::sync::CancellationToken> = None;
    let (poll_event_tx, mut poll_event_rx) = tokio::sync::mpsc::channel::<PollEvent>(16);

    loop {
        let check_interval = std::time::Duration::from_secs(1);

        tokio::select! {
            incoming = receiver.next() => {
                let text = match incoming {
                    Some(Ok(Message::Text(text))) => text,
                    Some(Ok(Message::Close(_))) | None => break,
                    Some(Ok(Message::Ping(data))) => {
                        let _ = sender.send(Message::Pong(data)).await;
                        continue;
                    }
                    Some(Err(_)) | Some(Ok(_)) => continue,
                };

                let inbound: BindInboundMessage = match serde_json::from_str(&text) {
                    Ok(m) => m,
                    Err(_) => {
                        let err = BindOutboundMessage::Error {
                            channel: channel_name.clone(),
                            message: "Invalid message format".to_string(),
                        };
                        let _ = send_json(&mut sender, &err).await;
                        continue;
                    }
                };

                match inbound {
                    BindInboundMessage::Start | BindInboundMessage::Refresh => {
                        if let Some(ct) = poll_cancel.take() {
                            ct.cancel();
                        }
                        active_qr_key = None;
                        active_qr_expires = None;

                        match channel_name.as_str() {
                            "weixin" => {
                                handle_weixin_start(
                                    &mut sender,
                                    &channel_name,
                                    &config,
                                    &mut active_qr_key,
                                    &mut active_qr_expires,
                                ).await;
                            }
                            _ => {}
                        }
                    }
                }
            }

            poll_event = poll_event_rx.recv() => {
                let event = match poll_event {
                    Some(e) => e,
                    None => continue,
                };

                match event {
                    PollEvent::Scaned => {
                        let msg = BindOutboundMessage::Scaned {
                            channel: channel_name.clone(),
                        };
                        let _ = send_json(&mut sender, &msg).await;
                    }
                    PollEvent::Confirmed { account_id, user_id } => {
                        active_qr_key = None;
                        active_qr_expires = None;
                        if let Some(ct) = poll_cancel.take() {
                            ct.cancel();
                        }
                        let msg = BindOutboundMessage::BindSuccess {
                            channel: channel_name.clone(),
                            account_id,
                            user_id,
                        };
                        let _ = send_json(&mut sender, &msg).await;
                    }
                    PollEvent::Expired => {
                        active_qr_key = None;
                        active_qr_expires = None;
                        if let Some(ct) = poll_cancel.take() {
                            ct.cancel();
                        }
                        let msg = BindOutboundMessage::QrExpired {
                            channel: channel_name.clone(),
                        };
                        let _ = send_json(&mut sender, &msg).await;
                    }
                }
            }

            _ = tokio::time::sleep(check_interval) => {
                if let (Some(key), Some(expires)) = (&active_qr_key, active_qr_expires) {
                    if now_epoch_secs() >= expires {
                        let expired_msg = BindOutboundMessage::QrExpired {
                            channel: channel_name.clone(),
                        };
                        let _ = send_json(&mut sender, &expired_msg).await;
                        active_qr_key = None;
                        active_qr_expires = None;
                        if let Some(ct) = poll_cancel.take() {
                            ct.cancel();
                        }
                    } else if poll_cancel.is_none() || poll_cancel.as_ref().is_some_and(|ct| ct.is_cancelled()) {
                        let key_clone = key.clone();
                        let ct = tokio_util::sync::CancellationToken::new();
                        poll_cancel = Some(ct.clone());

                        let weixin = create_weixin_channel(&config);
                        let config_path = config.config_path.to_string_lossy().to_string();
                        let event_tx = poll_event_tx.clone();

                        tokio::spawn(poll_weixin_status(
                            weixin,
                            key_clone,
                            expires,
                            ct,
                            config_path,
                            event_tx,
                        ));
                    }
                }
            }
        }
    }

    if let Some(ct) = poll_cancel.take() {
        ct.cancel();
    }
}

fn create_weixin_channel(config: &zeroclaw_config::schema::Config) -> WeixinChannel {
    let weixin_cfg = match &config.channels.weixin {
        Some(cfg) => cfg,
        None => {
            return WeixinChannel::new(String::new(), vec![]);
        }
    };
    let base_url = weixin_cfg
        .base_url
        .clone()
        .unwrap_or_else(|| "https://ilinkai.weixin.qq.com".to_string());

    WeixinChannel::new_with_base_url(base_url, String::new(), vec![])
        .with_proxy_url(weixin_cfg.proxy_url.clone())
}

async fn handle_weixin_start(
    sender: &mut futures_util::stream::SplitSink<WebSocket, Message>,
    channel: &str,
    config: &zeroclaw_config::schema::Config,
    active_qr_key: &mut Option<String>,
    active_qr_expires: &mut Option<u64>,
) {
    let weixin_cfg = config.channels.weixin.as_ref();
    tracing::info!(
        "Weixin bind: starting QR code fetch, weixin_configured={}, base_url={:?}, proxy={:?}",
        weixin_cfg.is_some(),
        weixin_cfg.and_then(|c| c.base_url.as_deref()),
        weixin_cfg.and_then(|c| c.proxy_url.as_deref()),
    );

    let weixin = create_weixin_channel(config);

    let (qrcode_key, qrcode_img_url) = match weixin.fetch_qr_code().await {
        Ok(result) => result,
        Err(e) => {
            tracing::error!("Weixin bind: fetch_qr_code failed: {e}");
            let err = BindOutboundMessage::Error {
                channel: channel.to_string(),
                message: format!("Failed to get QR code: {e}"),
            };
            let _ = send_json(sender, &err).await;
            return;
        }
    };

    tracing::info!(
        "Weixin bind: qrcode_key={}, img_url_type={}, url_preview={}",
        qrcode_key,
        if qrcode_img_url.starts_with("data:image") {
            "data_uri"
        } else if qrcode_img_url.starts_with("http") {
            "url"
        } else {
            "unknown"
        },
        &qrcode_img_url[..qrcode_img_url.len().min(120)]
    );

    let image_base64 = if qrcode_img_url.starts_with("data:image") {
        Some(qrcode_img_url)
    } else {
        match zeroclaw_channels::weixin::WeixinChannel::generate_qr_png_base64(&qrcode_img_url) {
            Ok(b64) => Some(b64),
            Err(e) => {
                tracing::warn!("Weixin bind: qrencode failed, fallback url mode: {e}");
                None
            }
        }
    };

    let expires_at = now_epoch_secs() + WEIXIN_QR_EXPIRY_SECS;

    let Some(ref b64) = image_base64 else {
        let err = BindOutboundMessage::Error {
            channel: channel.to_string(),
            message: "Failed to generate QR code".to_string(),
        };
        let _ = send_json(sender, &err).await;
        return;
    };

    let qr_msg = BindOutboundMessage::QrCode {
        channel: channel.to_string(),
        image_base64: b64.clone(),
        expires_at,
    };
    if send_json(sender, &qr_msg).await.is_err() {
        return;
    }

    *active_qr_key = Some(qrcode_key);
    *active_qr_expires = Some(expires_at);
}

async fn poll_weixin_status(
    weixin: WeixinChannel,
    qrcode_key: String,
    expires_at: u64,
    cancel: tokio_util::sync::CancellationToken,
    config_path: String,
    event_tx: tokio::sync::mpsc::Sender<PollEvent>,
) {
    let deadline =
        std::time::Instant::now() + std::time::Duration::from_secs(WEIXIN_POLL_TIMEOUT_SECS);

    while std::time::Instant::now() < deadline {
        if cancel.is_cancelled() {
            return;
        }

        if now_epoch_secs() >= expires_at {
            let _ = event_tx.send(PollEvent::Expired).await;
            return;
        }

        tokio::select! {
            _ = tokio::time::sleep(std::time::Duration::from_secs(WEIXIN_POLL_INTERVAL_SECS)) => {}
            _ = cancel.cancelled() => {
                return;
            }
        }

        if cancel.is_cancelled() {
            return;
        }

        match weixin.poll_qr_status(&qrcode_key).await {
            Ok(QrPollStatus::Confirmed(login_result)) => {
                if let Some(ref token) = login_result.bot_token {
                    if !config_path.is_empty() {
                        if let Err(e) = weixin.save_bot_token_to_config(&config_path, token).await {
                            tracing::error!("Weixin bind: failed to save token: {e}");
                        }
                    }
                }
                let _ = event_tx
                    .send(PollEvent::Confirmed {
                        account_id: login_result.account_id,
                        user_id: login_result.user_id,
                    })
                    .await;
                return;
            }
            Ok(QrPollStatus::Expired) => {
                let _ = event_tx.send(PollEvent::Expired).await;
                return;
            }
            Ok(QrPollStatus::Scaned) => {
                let _ = event_tx.send(PollEvent::Scaned).await;
            }
            Ok(QrPollStatus::Redirect { host }) => {
                tracing::info!("Weixin bind: IDC redirect to {}", host);
            }
            Ok(QrPollStatus::Waiting) => {}
            Err(e) => {
                tracing::warn!("Weixin bind: poll error: {e}");
            }
        }
    }
}

async fn send_json(
    sender: &mut futures_util::stream::SplitSink<WebSocket, Message>,
    msg: &BindOutboundMessage,
) -> Result<(), ()> {
    let json = serde_json::to_string(msg).map_err(|_| ())?;
    sender
        .send(Message::Text(json.into()))
        .await
        .map_err(|_| ())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bind_outbound_serialization() {
        let msg = BindOutboundMessage::QrCode {
            channel: "weixin".to_string(),
            image_base64: "data:image/png;base64,abc".to_string(),
            expires_at: 1234567890,
        };
        let json = serde_json::to_string(&msg).unwrap();
        assert!(json.contains("\"type\":\"qr_code\""));
        assert!(json.contains("\"channel\":\"weixin\""));

        let expired = BindOutboundMessage::QrExpired {
            channel: "weixin".to_string(),
        };
        let json = serde_json::to_string(&expired).unwrap();
        assert!(json.contains("\"type\":\"qr_expired\""));

        let scaned = BindOutboundMessage::Scaned {
            channel: "weixin".to_string(),
        };
        let json = serde_json::to_string(&scaned).unwrap();
        assert!(json.contains("\"type\":\"scaned\""));

        let success = BindOutboundMessage::BindSuccess {
            channel: "weixin".to_string(),
            account_id: Some("bot123".to_string()),
            user_id: Some("user456".to_string()),
        };
        let json = serde_json::to_string(&success).unwrap();
        assert!(json.contains("\"type\":\"bind_success\""));
    }

    #[test]
    fn test_bind_inbound_deserialization() {
        let start: BindInboundMessage = serde_json::from_str("{\"type\":\"start\"}").unwrap();
        assert!(matches!(start, BindInboundMessage::Start));

        let refresh: BindInboundMessage = serde_json::from_str("{\"type\":\"refresh\"}").unwrap();
        assert!(matches!(refresh, BindInboundMessage::Refresh));
    }
}
