use crate::client::http::ReqwestGateway;
use crate::client::poller::FunPayPoller;
use crate::client::FunpayGateway;
use crate::config::FunPayConfig;
use crate::error::FunPayError;
use crate::events::Event;
use crate::models::enums::SubcategoryType;
use crate::models::ids::ChatId;
use crate::models::{
    CategoryFilter, CategorySubcategory, MarketOffer, Message, Offer, OfferEditParams,
    OfferFullParams, Order, OrderShortcut, Subcategory,
};
use crate::parsing::{
    parse_category_filters, parse_category_subcategories, parse_market_offers, parse_message_html,
    parse_my_offers, parse_offer_edit_params, parse_offer_full_params, parse_order_page,
    parse_order_secrets, parse_orders_list,
};
use crate::storage::json::JsonFileStorage;
use crate::storage::memory::InMemoryStorage;
use crate::storage::StateStorage;
use crate::utils::{extract_phpsessid, random_tag};
use regex::Regex;
use scraper::{Html, Selector};
use serde_json::{json, to_string, Value};
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::broadcast::{self, Sender};

#[derive(Debug, Clone)]
struct AppData {
    user_id: i64,
    csrf_token: String,
}

pub struct FunPayAccount {
    gateway: Arc<dyn FunpayGateway>,
    pub golden_key: String,
    user_agent: String,
    pub id: Option<i64>,
    pub username: Option<String>,
    pub csrf_token: Option<String>,
    phpsessid: Option<String>,
    locale: Option<String>,
    pub events_tx: Sender<Event>,
    sorted_subcategories: HashMap<SubcategoryType, HashMap<i64, Subcategory>>,
    storage: Arc<dyn StateStorage>,
    polling_interval: Duration,
    error_retry_delay: Duration,
}

impl fmt::Debug for FunPayAccount {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("FunPayAccount")
            .field("golden_key", &"[redacted]")
            .field("user_agent", &self.user_agent)
            .field("id", &self.id)
            .field("username", &self.username)
            .finish()
    }
}

#[derive(Clone)]
pub struct FunPaySender {
    gateway: Arc<dyn FunpayGateway>,
    golden_key: String,
    user_agent: String,
    csrf_token: String,
    phpsessid: Option<String>,
    seller_id: i64,
}

impl FunPaySender {
    pub fn seller_id(&self) -> i64 {
        self.seller_id
    }
}

impl FunPayAccount {
    pub fn new(golden_key: String) -> Self {
        Self::with_config(golden_key, FunPayConfig::default())
    }

    pub fn with_config(golden_key: String, config: FunPayConfig) -> Self {
        let gateway: Arc<dyn FunpayGateway> = Arc::new(ReqwestGateway::with_config(&config));
        Self::with_gateway_and_config(gateway, golden_key, config)
    }

    pub fn with_proxy(golden_key: String, proxy_url: &str) -> Self {
        Self::with_proxy_and_config(golden_key, proxy_url, FunPayConfig::default())
    }

    pub fn with_proxy_and_config(golden_key: String, proxy_url: &str, config: FunPayConfig) -> Self {
        let gateway: Arc<dyn FunpayGateway> =
            Arc::new(ReqwestGateway::with_proxy_and_config(proxy_url, &config));
        Self::with_gateway_and_config(gateway, golden_key, config)
    }

    pub fn with_gateway(gateway: Arc<dyn FunpayGateway>, golden_key: String) -> Self {
        Self::with_gateway_and_config(gateway, golden_key, FunPayConfig::default())
    }

    pub fn with_gateway_and_config(
        gateway: Arc<dyn FunpayGateway>,
        golden_key: String,
        config: FunPayConfig,
    ) -> Self {
        let (tx, _rx) = broadcast::channel(config.event_channel_capacity);
        let storage: Arc<dyn StateStorage> = match config.state_storage_path {
            Some(ref path) => Arc::new(JsonFileStorage::new(path.clone())),
            None => Arc::new(InMemoryStorage::new()),
        };
        Self {
            gateway,
            golden_key,
            user_agent: config.user_agent.clone(),
            id: None,
            username: None,
            csrf_token: None,
            phpsessid: None,
            locale: None,
            events_tx: tx,
            sorted_subcategories: HashMap::new(),
            storage,
            polling_interval: config.polling_interval,
            error_retry_delay: config.error_retry_delay,
        }
    }

    pub fn subscribe(&self) -> broadcast::Receiver<Event> {
        self.events_tx.subscribe()
    }

    pub async fn init(&mut self) -> Result<(), FunPayError> {
        self.get().await
    }

    pub fn create_sender(&self) -> Result<FunPaySender, FunPayError> {
        let csrf = self
            .csrf_token
            .as_ref()
            .ok_or(FunPayError::AccountNotInitiated)?
            .to_string();
        let seller_id = self.id.ok_or(FunPayError::AccountNotInitiated)?;
        Ok(FunPaySender {
            gateway: self.gateway.clone(),
            golden_key: self.golden_key.clone(),
            user_agent: self.user_agent.clone(),
            csrf_token: csrf,
            phpsessid: self.phpsessid.clone(),
            seller_id,
        })
    }

    async fn get(&mut self) -> Result<(), FunPayError> {
        let (body, set_cookies) = self
            .gateway
            .get_home(&self.golden_key, &self.user_agent)
            .await?;
        if let Some(sess) = extract_phpsessid(&set_cookies) {
            self.phpsessid = Some(sess);
        }
        let html = Html::parse_document(&body);
        let sel_body = Selector::parse("body").unwrap();
        let mut app_data: Option<AppData> = None;
        if let Some(b) = html.select(&sel_body).next() {
            if let Some(attr) = b.value().attr("data-app-data") {
                let v: Value =
                    serde_json::from_str(attr).map_err(|e| FunPayError::Parse(e.to_string()))?;
                let user_id = v
                    .get("userId")
                    .and_then(|x| x.as_i64())
                    .ok_or_else(|| FunPayError::Parse(String::from("missing userId")))?;
                let csrf = v
                    .get("csrf-token")
                    .and_then(|x| x.as_str())
                    .ok_or_else(|| FunPayError::Parse(String::from("missing csrf-token")))?;
                if let Some(loc) = v.get("locale").and_then(|x| x.as_str()) {
                    self.locale = Some(loc.to_string());
                }
                app_data = Some(AppData {
                    user_id,
                    csrf_token: csrf.to_string(),
                });
            }
        }
        let sel_uname = Selector::parse("div.user-link-name").unwrap();
        let username = html
            .select(&sel_uname)
            .next()
            .map(|n| n.text().collect::<String>());
        if username.is_none() {
            return Err(FunPayError::Unauthorized);
        }
        let app = app_data.ok_or_else(|| FunPayError::Parse(String::from("missing app data")))?;
        self.id = Some(app.user_id);
        self.csrf_token = Some(app.csrf_token);
        self.username = username;
        self.setup_subcategories(&body);
        Ok(())
    }

    fn setup_subcategories(&mut self, html: &str) {
        let doc = Html::parse_document(html);
        let sel_lists = Selector::parse("div.promo-game-list").unwrap();
        let mut lists: Vec<_> = doc.select(&sel_lists).collect();
        if lists.is_empty() {
            return;
        }
        let container = if lists.len() > 1 {
            lists.remove(1)
        } else {
            lists.remove(0)
        };
        let sel_item = Selector::parse("div.promo-game-item").unwrap();
        let sel_ul = Selector::parse("ul.list-inline").unwrap();
        let sel_a = Selector::parse("a").unwrap();
        let re_id = Regex::new(r"/(?:chips|lots)/(\d+)/?").unwrap();
        for game in container.select(&sel_item) {
            for ul in game.select(&sel_ul) {
                for li in ul.children() {
                    if let Some(el) = li.value().as_element() {
                        if el.name() != "li" {
                            continue;
                        }
                    } else {
                        continue;
                    }
                    if let Some(a) = li
                        .first_child()
                        .and_then(|n| n.value().as_element())
                        .and_then(|_| ul.select(&sel_a).next())
                    {
                        let name = a.text().collect::<String>().trim().to_string();
                        if name.is_empty() {
                            continue;
                        }
                        let href = a.value().attr("href").unwrap_or("");
                        let typ = if href.contains("chips/") {
                            SubcategoryType::Currency
                        } else {
                            SubcategoryType::Common
                        };
                        let id = re_id
                            .captures(href)
                            .and_then(|c| c.get(1))
                            .and_then(|m| m.as_str().parse::<i64>().ok());
                        if let Some(sid) = id {
                            let sub = Subcategory {
                                id: Some(sid),
                                name: name.clone(),
                            };
                            let entry = self.sorted_subcategories.entry(typ).or_default();
                            entry.insert(sid, sub);
                        }
                    }
                }
            }
        }
    }

    pub async fn start_polling_loop(&mut self) -> Result<(), FunPayError> {
        let poller = FunPayPoller {
            gateway: self.gateway.clone(),
            golden_key: self.golden_key.clone(),
            user_agent: self.user_agent.clone(),
            id: self.id.ok_or(FunPayError::AccountNotInitiated)?,
            username: self.username.clone(),
            csrf_token: self
                .csrf_token
                .clone()
                .ok_or(FunPayError::AccountNotInitiated)?,
            phpsessid: self.phpsessid.clone(),
            events_tx: self.events_tx.clone(),
            storage: self.storage.clone(),
            polling_interval: self.polling_interval,
            error_retry_delay: self.error_retry_delay,
            last_msg_event_tag: random_tag(),
            last_order_event_tag: random_tag(),
            last_messages: HashMap::new(),
            last_messages_ids: HashMap::new(),
            saved_orders: HashMap::new(),
        };
        poller.start().await
    }
}

impl FunPaySender {
    pub async fn send_chat_message(&self, chat_id: &str, content: &str) -> Result<(), FunPayError> {
        let mut csrf_to_use = self.csrf_token.clone();
        let mut phpsess_to_use = self.phpsessid.clone();
        if phpsess_to_use.is_none() {
            let (body, set_cookies) = self
                .gateway
                .get_chat_page(&self.golden_key, &self.user_agent, chat_id)
                .await?;
            let html = Html::parse_document(&body);
            let sel_body = Selector::parse("body").unwrap();
            if let Some(b) = html.select(&sel_body).next() {
                if let Some(attr) = b.value().attr("data-app-data") {
                    if let Ok(v) = serde_json::from_str::<Value>(attr) {
                        if let Some(csrf) = v.get("csrf-token").and_then(|x| x.as_str()) {
                            csrf_to_use = csrf.to_string();
                        }
                    }
                }
            }
            if let Some(sess) = extract_phpsessid(&set_cookies) {
                phpsess_to_use = Some(sess);
            }
        }
        let objects_json = to_string(&vec![serde_json::json!({
            "type": "chat_node",
            "id": chat_id,
            "tag": "00000000",
            "data": {"node": chat_id, "last_message": -1, "content": ""}
        })])
        .unwrap();
        let request_json = json!({
            "action": "chat_message",
            "data": {"node": chat_id, "last_message": -1, "content": content}
        })
        .to_string();
        self.gateway
            .post_runner(
                &self.golden_key,
                &self.user_agent,
                &csrf_to_use,
                phpsess_to_use.as_deref(),
                &objects_json,
                Some(&request_json),
            )
            .await
            .map(|_| ())
    }

    pub async fn get_chat_messages(&self, chat_id: &str) -> Result<Vec<Message>, FunPayError> {
        let objects_json = to_string(&vec![serde_json::json!({
            "type": "chat_node",
            "id": chat_id,
            "tag": "00000000",
            "data": {"node": chat_id, "last_message": -1, "content": ""}
        })])
        .unwrap();

        let res = self
            .gateway
            .post_runner(
                &self.golden_key,
                &self.user_agent,
                &self.csrf_token,
                self.phpsessid.as_deref(),
                &objects_json,
                None,
            )
            .await?;

        let objects = res
            .get("objects")
            .and_then(|x| x.as_array())
            .cloned()
            .unwrap_or_default();

        for obj in objects {
            if obj.get("type").and_then(|x| x.as_str()) != Some("chat_node") {
                continue;
            }

            let data = match obj.get("data") {
                Some(d) => d,
                None => continue,
            };

            let messages = data
                .get("messages")
                .and_then(|x| x.as_array())
                .cloned()
                .unwrap_or_default();

            let mut list = Vec::new();
            for m in messages {
                let mid = m.get("id").and_then(|x| x.as_i64()).unwrap_or(0);
                let author_id = m.get("author").and_then(|x| x.as_i64()).unwrap_or(0);
                let html = m.get("html").and_then(|x| x.as_str()).unwrap_or("");
                let (text, _image) = parse_message_html(html);
                list.push(Message {
                    id: mid,
                    chat_id: ChatId::from(chat_id.to_string()),
                    chat_name: None,
                    text,
                    interlocutor_id: None,
                    author_id,
                });
            }
            return Ok(list);
        }

        Ok(Vec::new())
    }

    pub fn get_chat_id_for_user(&self, user_id: i64) -> String {
        let my_id = self.seller_id;
        let (id1, id2) = (my_id.min(user_id), my_id.max(user_id));
        format!("users-{id1}-{id2}")
    }

    pub async fn get_order_secrets(&self, order_id: &str) -> Result<Vec<String>, FunPayError> {
        let body = self
            .gateway
            .get_order_page(&self.golden_key, &self.user_agent, order_id)
            .await?;
        let doc = Html::parse_document(&body);
        Ok(parse_order_secrets(&doc))
    }

    pub async fn get_order(&self, order_id: &str) -> Result<Order, FunPayError> {
        let body = self
            .gateway
            .get_order_page(&self.golden_key, &self.user_agent, order_id)
            .await?;
        parse_order_page(&body, order_id)
    }

    pub async fn edit_offer(
        &self,
        offer_id: i64,
        node_id: i64,
        params: OfferEditParams,
    ) -> Result<Value, FunPayError> {
        let html = self
            .gateway
            .get_offer_edit_page(&self.golden_key, &self.user_agent, node_id, offer_id)
            .await?;

        let current = parse_offer_edit_params(&html);
        log::debug!(
            target: "funpay_client",
            "Parsed offer {} current params: quantity={:?}, method={:?}, price={:?}",
            offer_id,
            current.quantity,
            current.method,
            current.price
        );
        let merged = current.merge(params);
        log::debug!(
            target: "funpay_client",
            "Merged offer {} params: quantity={:?}, method={:?}, price={:?}",
            offer_id,
            merged.quantity,
            merged.method,
            merged.price
        );

        self.gateway
            .post_offer_save(
                &self.golden_key,
                &self.user_agent,
                self.phpsessid.as_deref(),
                &self.csrf_token,
                offer_id,
                node_id,
                &merged,
            )
            .await
    }

    pub async fn get_offer_params(
        &self,
        offer_id: i64,
        node_id: i64,
    ) -> Result<OfferFullParams, FunPayError> {
        let html = self
            .gateway
            .get_offer_edit_page(&self.golden_key, &self.user_agent, node_id, offer_id)
            .await?;
        Ok(parse_offer_full_params(&html, offer_id, node_id))
    }

    pub async fn get_my_offers(&self, node_id: i64) -> Result<Vec<Offer>, FunPayError> {
        let html = self
            .gateway
            .get_lots_trade_page(&self.golden_key, &self.user_agent, node_id)
            .await?;
        Ok(parse_my_offers(&html, node_id))
    }

    pub async fn get_market_offers(&self, node_id: i64) -> Result<Vec<MarketOffer>, FunPayError> {
        let html = self
            .gateway
            .get_lots_page(&self.golden_key, &self.user_agent, node_id)
            .await?;
        Ok(parse_market_offers(&html, node_id))
    }

    pub async fn get_orders(&self) -> Result<Vec<OrderShortcut>, FunPayError> {
        let body = self
            .gateway
            .get_orders_trade(&self.golden_key, &self.user_agent)
            .await?;
        parse_orders_list(&body, self.seller_id)
    }

    pub async fn get_category_subcategories(
        &self,
        node_id: i64,
    ) -> Result<Vec<CategorySubcategory>, FunPayError> {
        let html = self
            .gateway
            .get_lots_page(&self.golden_key, &self.user_agent, node_id)
            .await?;
        Ok(parse_category_subcategories(&html))
    }

    pub async fn get_category_filters(
        &self,
        node_id: i64,
    ) -> Result<Vec<CategoryFilter>, FunPayError> {
        let html = self
            .gateway
            .get_lots_page(&self.golden_key, &self.user_agent, node_id)
            .await?;
        Ok(parse_category_filters(&html))
    }
}
