package cn.xpleaf.spider.core.parser.Impl;

import cn.xpleaf.spider.constants.SpiderConstants;
import cn.xpleaf.spider.core.parser.IParser;
import cn.xpleaf.spider.core.pojo.Page;
import cn.xpleaf.spider.utils.HtmlUtil;
import cn.xpleaf.spider.utils.HttpUtil;
import cn.xpleaf.spider.utils.SpiderUtil;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * 解析京东商品的实现类
 */
public class JDHtmlParserImpl implements IParser {

    // log4j日志记录
    private Logger logger = LoggerFactory.getLogger(JDHtmlParserImpl.class);

    @Override
    public void parser(Page page) {
        HtmlCleaner cleaner = new HtmlCleaner();
        /**
         * cleaner.clean()方法,如果page.getContent为null,那么整个程序就会一直阻塞在这里
         * 所以,在前面的代码中ISpider.start()方法,下载网页后,需要对内容进行判断,如果content为空,则跳过解析
         */
        TagNode rootNode = cleaner.clean(page.getContent());

        long start = System.currentTimeMillis();    // 解析开始时间
        // 进行判断 根据url的类型进行列表解析还是商品解析
        if (page.getUrl().startsWith("https://item.jd.com/")) {  // 解析商品
            parserProduct(page, rootNode);
            logger.info("解析商品页面:{}, 消耗时长:{}ms", page.getUrl(), System.currentTimeMillis() - start);
        } else if (page.getUrl().startsWith("https://list.jd.com/list.html")) {  // 解析列表
            // 当前页面的商品url列表
            List<String> urls = HtmlUtil.getListUrlByXpath(rootNode, "href", "//div[@id='plist']/ul/li/div/div[1]/a");
            // 下一页 获取下一页的url
            String nextUrl = HtmlUtil.getAttrByXpath(rootNode, "href", "//div[@id='J_topPage']/a[2]");
            if (!"javascript:;".equals(nextUrl)) {    // 说明已经到最后一页了,再不能往下解析了,把当前的url进行排除
                nextUrl = "https://list.jd.com" + nextUrl;
                urls.add(nextUrl);
            }
            page.getUrls().addAll(urls);
            /**
             * 需要注意的是,当解析的是列表url时,该分支的代码只会解析当前页面的url,而不会爬取数据
             * url解析完成以后,添加到当前Page对象中的urls列表中,解析结束后,urls会被添加到Spider对象的url仓库中(高优先级队列)
             * 这样来让交给循环继续做解析,直到高把优先级队列的url都解析完成了,后面才会去解析低优先级也就是商品url的数据
             * 也就是说,当走的是解析列表的分支代码时,这时的Page对象的作用就变成了用来保存url的一个暂时的容器了
             */
            logger.info("解析列表页面:{}, 消耗时长:{}ms", page.getUrl(), System.currentTimeMillis() - start);
            if(System.currentTimeMillis() - start == 0) {   // 解析京东数据页码数时,偶尔获取不到下一页,时间就为0ms,这时需要重试
                logger.info("解析列表页面:{}, 消耗时长:{}ms, 尝试将其重新添加到高优先级url队列中", page.getUrl(), System.currentTimeMillis() - start);
                HttpUtil.retryUrl(page.getUrl(), SpiderUtil.getTopDomain(page.getUrl()) + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX);
            }
        }

    }

    private void parserProduct(Page page, TagNode tagNode) {
        // 1.id; 商品id
        String id = HtmlUtil.getIdByUrl(page.getUrl());
        page.setId(id);

        // 2.source; 商品来源
        String domain = SpiderUtil.getTopDomain(page.getUrl());
        page.setSource(domain);

        // 3.title; 商品标题
        String title = HtmlUtil.getTextByXpath(tagNode, "//div[@class='sku-name']");
        page.setTitle(title);

        // 4.price; 商品价格
        String priceUrl = "https://p.3.cn/prices/mgets?pduid=1504781656858214892980&skuIds=J_" + id;
        /**
         * 上面的价格url中,pduid每隔一段时间都会改变,所以需要定时更新一下,特别是价格无法获取到时则都是这个问题
         * 下面就来解决这个问题吧,如果获取不到价格,会返回jsonp字符串:{"error":"pdos_captcha"}1504781656858214892980
         * 当出现这个情况时,就提示更换pduid
         */
        String priceJson = HttpUtil.getHttpContent(priceUrl);
        if (priceJson != null) {
            // 解析json [{"op":"4899.00","m":"9999.00","id":"J_3133843","p":"4799.00"}] 将该json字符串封装成json对象
            if (priceJson.contains("error")) {   // 返回{"error":"pdos_captcha"},说明价格url已经不可用,更换pduid再做解析
                logger.info("价格url已经不可用,请及时更换pduid--->" + priceJson);
            } else {
                JSONArray priceJsonArray = new JSONArray(priceJson);
                JSONObject priceJsonObj = priceJsonArray.getJSONObject(0);
                String priceStr = priceJsonObj.getString("p").trim();
                Float price = Float.valueOf(priceStr);
                page.setPrice(price);
            }
        }

        // 5.imgUrl; 商品图片链接
        String imgUrl = HtmlUtil.getAttrByXpath(tagNode, "data-origin", "//img[@id='spec-img']");
        page.setImgUrl("http:" + imgUrl);

        // 6.params; 商品规格参数
        // Map<String, Map<String, String>>
        // {"主体": {"品牌": Apple}, "型号": "IPhone 7 Plus", "基本信息":{"机身颜色":"玫瑰金"}}
        JSONObject paramObj = HtmlUtil.getParams(tagNode, "//*[@id=\"detail\"]/div[2]/div[2]/div[1]/*", "//h3", "//dl");
        if (paramObj.has("主体")) {
            if (paramObj.getJSONObject("主体").has("品牌")) {
                String brand = paramObj.getJSONObject("主体").getString("品牌");
                page.setBrand(brand);
            }
        }
        page.setParams(paramObj.toString());

        // 7.商品评论数
        // 注意JsonObj和JsonArray的不同获取方法
        String commentCountUrl = "https://club.jd.com/comment/productCommentSummaries.action?referenceIds=" + id;
        String commentCountJson = HttpUtil.getHttpContent(commentCountUrl);
        if (commentCountJson != null) {
            JSONArray commentCountJsonArray = new JSONObject(commentCountJson).getJSONArray("CommentsCount");
            JSONObject commentCountJsonObj = commentCountJsonArray.getJSONObject(0);
            int commentCount = commentCountJsonObj.getInt("CommentCount");
            page.setCommentCount(commentCount);
        }
    }
}