/*
 * ============================================================================
 * 牛牛GEO内容推送协议 - 通用文章接收器（Java Spring Boot 版）
 * ============================================================================
 *
 * 协议版本：牛牛GEO内容推送协议 v1.0
 * 适用场景：Java（Spring Boot）技术栈的自建站官网
 *
 * 【Maven 依赖】在你的 pom.xml 中添加：
 *
 *   <dependency>
 *       <groupId>org.springframework.boot</groupId>
 *       <artifactId>spring-boot-starter-web</artifactId>
 *   </dependency>
 *   <dependency>
 *       <groupId>org.springframework.boot</groupId>
 *       <artifactId>spring-boot-starter-jdbc</artifactId>
 *   </dependency>
 *   <dependency>
 *       <groupId>com.mysql</groupId>
 *       <artifactId>mysql-connector-j</artifactId>
 *   </dependency>
 *
 * 【使用方式】
 *   1. 把本类放入你的 Spring Boot 项目（如已有主应用类，删掉本文件的 main
 *      方法与 @SpringBootApplication，只保留 Controller 部分即可）
 *   2. 修改下方【配置区域】（密钥、数据库、表名、字段映射）
 *   3. 启动应用后，接口地址为：https://你的域名/ngeo/push
 *   4. 在牛牛GEO平台「添加官网站点」时填写该地址、认证方式 Bearer Token、密钥
 *
 * 【协议行为】
 *   - POST {_action:"verify"}          连通性测试
 *   - GET 或 {_action:"channels"}      获取栏目列表
 *   - POST 完整文章载荷                 写入文章
 *
 * 【响应格式】统一为 {"code":0,"msg":"...","data":...}，code=0 表示成功
 *
 * @version 1.0.0
 */
package com.example.ngeo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.*;

import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.*;

@SpringBootApplication
@RestController
@RequestMapping("/ngeo")
public class NiuGeoReceiverApplication {

    // ========================================================================
    // 配置区域（请根据实际情况修改）
    // ========================================================================

    /** 认证密钥：必须修改！由你自己生成，并与牛牛GEO平台「添加站点」时填写的一致 */
    private static final String SECRET_KEY = "CHANGE_THIS_TO_YOUR_SECRET_KEY";

    /** 数据库配置 */
    private static final String DB_URL = "jdbc:mysql://127.0.0.1:3306/your_database?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "";

    /** 文章表名 */
    private static final String TABLE = "articles";

    /**
     * 字段映射：key = 协议推送过来的字段名（不要改），value = 你的数据库字段名。
     * 你的表里没有对应字段时，把 value 改成 ""（空字符串）即可跳过。
     */
    private static final Map<String, String> FIELD_MAPPING = new LinkedHashMap<>() {{
        put("title", "title");
        put("content", "content");           // 正文（HTML 格式）
        put("summary", "summary");           // 摘要
        put("category_id", "category_id");   // 栏目/分类 ID
        put("seo_keywords", "keywords");     // SEO 关键词
        put("seo_description", "description"); // SEO 描述
        put("thumbnail", "thumbnail");       // 封面图 URL
        put("images", "images");             // 配图数组（写入时自动转 JSON 字符串）
        put("tags", "tags");                 // 标签数组（写入时自动转 JSON 字符串）
        put("source_id", "source_id");       // 牛牛GEO平台文章唯一 ID（用于去重）
        put("status", "status");             // 发布状态：1=发布，0=草稿
    }};

    /** 额外默认值：每篇文章都会带上的固定字段值。值为 "" 的时间字段自动填当前时间戳 */
    private static final Map<String, Object> DEFAULTS = new LinkedHashMap<>() {{
        put("author", "牛牛GEO");
        put("create_time", ""); // 留空 = 自动填当前 Unix 时间戳
    }};

    /** 栏目表配置（用于「获取栏目」功能，TABLE 留空则返回空列表） */
    private static final String CATEGORY_TABLE = "categories";
    private static final String CATEGORY_ID_FIELD = "id";
    private static final String CATEGORY_NAME_FIELD = "name";
    private static final String CATEGORY_PARENT_FIELD = "parent_id";

    /** 幂等去重开关与字段（推荐开启） */
    private static final boolean DEDUP_ENABLE = true;
    private static final String DEDUP_FIELD = "source_id";

    // ========================================================================
    // 以下代码无需修改
    // ========================================================================

    public static void main(String[] args) {
        SpringApplication.run(NiuGeoReceiverApplication.class, args);
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl(DB_URL);
        ds.setUsername(DB_USER);
        ds.setPassword(DB_PASSWORD);
        return ds;
    }

    @Autowired
    private JdbcTemplate jdbc;

    private final ObjectMapper objectMapper = new ObjectMapper();

    /** 统一响应 */
    private Map<String, Object> reply(int code, String msg, Object data) {
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("code", code);
        r.put("msg", msg);
        r.put("data", data);
        return r;
    }

    /** 认证校验：支持 Bearer Token / X-API-Key / ?api_key= */
    private boolean authenticate(String authHeader, String apiKeyHeader, String apiKeyParam) {
        if ("CHANGE_THIS_TO_YOUR_SECRET_KEY".equals(SECRET_KEY)) return false;

        if (authHeader != null && authHeader.startsWith("Bearer ")
                && SECRET_KEY.equals(authHeader.substring(7))) {
            return true;
        }
        if (apiKeyHeader != null && SECRET_KEY.equals(apiKeyHeader)) {
            return true;
        }
        return apiKeyParam != null && SECRET_KEY.equals(apiKeyParam);
    }

    /** 行为一：连通性测试 + 行为三：推送文章（POST） */
    @PostMapping("/push")
    public Map<String, Object> handlePost(
            @RequestHeader(value = "Authorization", required = false) String authHeader,
            @RequestHeader(value = "X-API-Key", required = false) String apiKeyHeader,
            @RequestParam(value = "api_key", required = false) String apiKeyParam,
            @RequestBody Map<String, Object> input) {

        if (!authenticate(authHeader, apiKeyHeader, apiKeyParam)) {
            return reply(1001, "认证失败：密钥不正确", null);
        }

        // 连通性测试
        if ("verify".equals(input.get("_action"))) {
            return reply(0, "连接成功，Spring Boot 接收器工作正常", null);
        }

        // 获取栏目（POST 方式）
        if ("channels".equals(input.get("_action"))) {
            return listChannels();
        }

        // 推送文章
        Object title = input.get("title");
        if (title == null || title.toString().isEmpty()) {
            return reply(2001, "文章标题不能为空", null);
        }

        try {
            // 幂等去重：同一 source_id 重复推送直接返回已有记录
            Object sourceId = input.get("source_id");
            if (DEDUP_ENABLE && sourceId != null && !sourceId.toString().isEmpty()) {
                List<Map<String, Object>> rows = jdbc.queryForList(
                        "SELECT id FROM `" + TABLE + "` WHERE `" + DEDUP_FIELD + "` = ? LIMIT 1",
                        sourceId);
                if (!rows.isEmpty()) {
                    Map<String, Object> data = new LinkedHashMap<>();
                    data.put("id", rows.get(0).get("id"));
                    data.put("exists", true);
                    return reply(0, "文章已存在，跳过重复推送", data);
                }
            }

            // 字段映射
            Map<String, Object> insertData = new LinkedHashMap<>();
            for (Map.Entry<String, String> e : FIELD_MAPPING.entrySet()) {
                String dbField = e.getValue();
                if (dbField == null || dbField.isEmpty()) continue; // 空映射 = 跳过
                Object value = input.get(e.getKey());
                if (value == null) continue;
                // 数组类型（images / tags）统一序列化为 JSON 字符串存储
                if (value instanceof List || value instanceof Map) {
                    value = objectMapper.writeValueAsString(value);
                }
                insertData.put(dbField, value);
            }

            // 默认值字段
            for (Map.Entry<String, Object> e : DEFAULTS.entrySet()) {
                String field = e.getKey();
                Object value = e.getValue();
                if (field == null || field.isEmpty()) continue;
                boolean isTimeField = Arrays.asList("create_time", "created_at", "add_time", "update_time").contains(field);
                if ("".equals(value) && isTimeField) {
                    insertData.put(field, System.currentTimeMillis() / 1000);
                } else if (value != null && !"".equals(value)) {
                    insertData.put(field, value);
                }
            }

            if (insertData.isEmpty()) {
                return reply(2001, "没有有效的数据可写入，请检查 FIELD_MAPPING 配置", null);
            }

            // 写入数据库
            List<String> columns = new ArrayList<>(insertData.keySet());
            List<Object> values = new ArrayList<>(insertData.values());
            String sql = "INSERT INTO `" + TABLE + "` (`" + String.join("`, `", columns)
                    + "`) VALUES (" + String.join(", ", Collections.nCopies(columns.size(), "?")) + ")";

            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbc.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                for (int i = 0; i < values.size(); i++) {
                    ps.setObject(i + 1, values.get(i));
                }
                return ps;
            }, keyHolder);

            Number newId = keyHolder.getKey();
            Map<String, Object> data = new LinkedHashMap<>();
            data.put("id", newId == null ? null : newId.longValue());
            return reply(0, "文章接收成功", data);

        } catch (Exception ex) {
            return reply(3001, "写入数据库失败: " + ex.getMessage(), null);
        }
    }

    /** 行为二：获取栏目列表（GET） */
    @GetMapping("/push")
    public Map<String, Object> handleGet(
            @RequestHeader(value = "Authorization", required = false) String authHeader,
            @RequestHeader(value = "X-API-Key", required = false) String apiKeyHeader,
            @RequestParam(value = "api_key", required = false) String apiKeyParam) {

        if (!authenticate(authHeader, apiKeyHeader, apiKeyParam)) {
            return reply(1001, "认证失败：密钥不正确", null);
        }
        return listChannels();
    }

    /** 查询栏目列表 */
    private Map<String, Object> listChannels() {
        if (CATEGORY_TABLE == null || CATEGORY_TABLE.isEmpty()) {
            return reply(0, "success", Collections.emptyList());
        }
        try {
            String sql = "SELECT `" + CATEGORY_ID_FIELD + "` AS id, `" + CATEGORY_NAME_FIELD
                    + "` AS name, `" + CATEGORY_PARENT_FIELD + "` AS parent_id FROM `"
                    + CATEGORY_TABLE + "` ORDER BY `" + CATEGORY_ID_FIELD + "` ASC";
            return reply(0, "success", jdbc.queryForList(sql));
        } catch (Exception ex) {
            return reply(3001, "获取栏目失败: " + ex.getMessage(), null);
        }
    }
}
