use mad_hatter::Promptable;
use mad_hatter::llm::Promptable as _;
use mad_hatter::llm::CachedPrompt;

// ============================================================
// P0 tests (preserved)
// ============================================================

#[allow(dead_code)]
#[derive(Promptable)]
/// @render 你是一个知识提炼专家。请根据以下信息更新知识文档。
struct CaptureInput {
    #[markdown(skip)]
    end_marker: String,

    /// @render 当前知识内容
    knowledge: String,
    /// @render 近况
    recent: String,
    /// @render 当前增量
    current: String,
    /// @render 本次小结
    summary: String,
}

#[test]
fn test_capture_to_markdown() {
    let input = CaptureInput {
        end_marker: "END123".into(),
        knowledge: "已有知识...".into(),
        recent: "最近发生...".into(),
        current: "".into(),  // 空→应跳过
        summary: "本次总结...".into(),
    };
    let md = input.to_markdown();

    // struct级doc comment → 头部（原样输出，无// 前缀）
    assert!(md.starts_with("你是一个知识提炼专家"), "should start with struct doc comment");

    // 字段级doc comment → 单行String用inline格式
    assert!(md.contains("当前知识内容: 已有知识..."), "should have knowledge inline");

    assert!(md.contains("近况: 最近发生..."), "should have recent inline");

    // 空字段跳过
    assert!(!md.contains("当前增量"), "empty field should be skipped entirely");

    // skip字段不渲染
    assert!(!md.contains("END123"), "skip field should not appear");
    assert!(!md.contains("end_marker"), "skip field name should not appear");

    // 单行String → inline格式
    assert!(md.contains("本次小结: 本次总结..."), "should have summary inline");
}

#[test]
fn test_all_empty_fields() {
    let input = CaptureInput {
        end_marker: "TOKEN".into(),
        knowledge: "".into(),
        recent: "".into(),
        current: "".into(),
        summary: "".into(),
    };
    let md = input.to_markdown();
    // 只有头部，没有任何section
    assert!(md.contains("你是一个知识提炼专家"));
    assert!(!md.contains("###"));
}

#[derive(Promptable)]
struct SimpleStruct {
    /// @render 名称
    name: String,
    /// @render 描述
    description: String,
}

#[test]
fn test_no_struct_doc_comment() {
    let s = SimpleStruct {
        name: "Alice".into(),
        description: "An AI engine".into(),
    };
    let md = s.to_markdown();
    // 无struct级doc → 没有头部文本，单行String用inline格式
    assert!(md.contains("名称: Alice"));
    assert!(md.contains("描述: An AI engine"));
}

#[test]
fn test_field_without_doc_uses_field_name() {
    #[derive(Promptable)]
    struct NoDocs {
        title: String,
    }
    let s = NoDocs { title: "Hello".into() };
    let md = s.to_markdown();
    // 无字段doc → 用字段名，单行String用inline格式
    assert!(md.contains("title: Hello"));
}

// ============================================================
// P2 tests: nested struct
// ============================================================

#[derive(Promptable)]
struct Inner {
    /// @render 发送者
    sender: String,
    /// @render 内容
    content: String,
}

#[derive(Promptable)]
/// @render 顶层结构
struct Outer {
    /// @render 标题
    title: String,
    /// @render 详细信息
    detail: Inner,
}

#[test]
fn test_nested_struct() {
    let o = Outer {
        title: "一些标题文本".into(),
        detail: Inner {
            sender: "alice".into(),
            content: "你好".into(),
        },
    };
    let md = o.to_markdown();

    // 顶层header（原样输出，无// 前缀）
    assert!(md.contains("顶层结构"), "should have struct doc header");

    // 顶层String字段：单行用inline格式
    assert!(md.contains("标题: 一些标题文本"), "title should use inline format");

    // 嵌套struct字段的section标题仍用 ### (FieldKind::Other)
    assert!(md.contains("### 详细信息 ###"), "nested field should have ### heading");

    // 嵌套struct内部的String字段：单行用inline格式
    assert!(md.contains("发送者: alice"), "nested struct string field should use inline format");
    assert!(md.contains("内容: 你好"), "nested struct string field should use inline format");
}

// ============================================================
// P2 tests: Vec field
// ============================================================

#[derive(Promptable)]
struct Message {
    /// @render 发送者标识
    sender: String,
    /// @render 消息内容
    content: String,
}

#[derive(Promptable)]
struct Chat {
    /// @render 最近的对话消息
    messages: Vec<Message>,
}

#[test]
fn test_vec_field() {
    let chat = Chat {
        messages: vec![
            Message { sender: "alice".into(), content: "你好".into() },
            Message { sender: "bob".into(), content: "世界".into() },
        ],
    };
    let md = chat.to_markdown();

    // Section标题
    assert!(md.contains("### 最近的对话消息 ###"), "should have vec section title");

    // Elements use depth mode: @render titles as labels
    assert!(md.contains("发送者标识: alice"), "first element sender with @render title");
    assert!(md.contains("消息内容: 你好"), "first element content with @render title");
    assert!(md.contains("发送者标识: bob"), "second element sender with @render title");
    assert!(md.contains("消息内容: 世界"), "second element content with @render title");

    // 注释只出现一次（section标题），不在每个元素重复
    let count = md.matches("最近的对话消息").count();
    assert_eq!(count, 1, "doc comment should appear only once");
}

#[test]
fn test_empty_vec_skipped() {
    let chat = Chat {
        messages: vec![],
    };
    let md = chat.to_markdown();

    // 空Vec → 整个字段不渲染
    assert!(!md.contains("最近的对话消息"), "empty vec should be skipped");
    assert!(!md.contains("###"), "no headings for empty struct");
}

// ============================================================
// P2 tests: basic types
// ============================================================

#[derive(Promptable)]
struct Status {
    /// @render 未读消息数
    unread_count: usize,
    /// @render HTTP端口
    port: u16,
    /// @render 是否在线
    online: bool,
}

#[test]
fn test_basic_types() {
    let s = Status {
        unread_count: 5,
        port: 8080,
        online: true,
    };
    let md = s.to_markdown();

    // Bool/Numeric fields use inline format (not section headings)
    assert!(md.contains("未读消息数: 5"), "usize inline");
    assert!(md.contains("HTTP端口: 8080"), "u16 inline");
    assert!(md.contains("是否在线: true"), "bool inline");
}

// ============================================================
// P2 tests: Option<non-String>
// ============================================================

#[derive(Promptable)]
struct Config {
    /// @render 超时时间
    timeout: Option<u64>,
    /// @render 名称
    name: Option<String>,
}

#[test]
fn test_option_non_string() {
    let c = Config {
        timeout: Some(30),
        name: Some("test".into()),
    };
    let md = c.to_markdown();

    // Option<u64> uses inline format (not section heading)
    assert!(md.contains("超时时间: 30"), "Option<u64> Some inline");
    assert!(md.contains("名称: test"), "Option<String> Some inline format");
}

#[test]
fn test_option_none_skipped() {
    let c = Config {
        timeout: None,
        name: None,
    };
    let md = c.to_markdown();

    assert!(!md.contains("超时时间"), "Option<u64> None should be skipped");
    assert!(!md.contains("名称"), "Option<String> None should be skipped");
}

// ============================================================
// P2 tests: mixed nested (simplified BeatRequest)
// ============================================================

#[derive(Promptable)]
struct PromptMsg {
    role: String,
    sender: String,
    content: String,
}

#[derive(Promptable)]
struct SessionEntry {
    messages: Vec<PromptMsg>,
    /// @render 小结
    summary: String,
}

#[derive(Promptable)]
struct SessionBlock {
    block_name: String,
    entries: Vec<SessionEntry>,
}

#[allow(dead_code)]
#[derive(Promptable)]
/// @render 你醒了
struct BeatRequestMini {
    #[markdown(skip)]
    action_token: String,
    /// @render 你的知识
    knowledge: String,
    /// @render 近况
    session_blocks: Vec<SessionBlock>,
    /// @render 当前状态
    current: String,
}

#[test]
fn test_mixed_nested() {
    let req = BeatRequestMini {
        action_token: "TOKEN123".into(),
        knowledge: "一些知识".into(),
        session_blocks: vec![
            SessionBlock {
                block_name: "session1".into(),
                entries: vec![
                    SessionEntry {
                        messages: vec![
                            PromptMsg {
                                role: "user".into(),
                                sender: "alice".into(),
                                content: "hello".into(),
                            },
                        ],
                        summary: "对话总结".into(),
                    },
                ],
            },
        ],
        current: "当前状态内容".into(),
    };
    let md = req.to_markdown();

    // Header（原样输出，无// 前缀）
    assert!(md.contains("你醒了"), "struct doc header");

    // Skip field
    assert!(!md.contains("TOKEN123"), "skip field should not appear");

    // String fields: single-line uses inline format
    assert!(md.contains("你的知识: 一些知识"), "knowledge inline");

    // Vec<SessionBlock> section
    assert!(md.contains("### 近况 ###"), "session_blocks heading");

    // Vec elements use compact format
    assert!(md.contains("block_name: session1"), "block_name in compact format");

    // Nested Vec<PromptMsg> elements
    assert!(md.contains("role: user"), "nested vec element");
    assert!(md.contains("sender: alice"), "nested vec element");
    assert!(md.contains("content: hello"), "nested vec element");

    // summary field
    assert!(md.contains("summary: 对话总结") || md.contains("小结"), "summary in compact format");

    // Current field - 单行String → inline格式
    assert!(md.contains("当前状态: 当前状态内容"), "current inline");
}

// ============================================================
// P2 tests: Vec<Struct> with struct-level prompt header
// ============================================================

#[derive(Promptable)]
/// @render 消息
struct ReadMsgEntry {
    /// @render 发送者
    sender: String,
    /// @render 内容
    content: String,
}

#[derive(Promptable)]
struct ReadAction {
    /// @render 收到的消息
    entries: Vec<ReadMsgEntry>,
}

#[test]
fn test_vec_struct_with_header() {
    let action = ReadAction {
        entries: vec![
            ReadMsgEntry { sender: "alice".into(), content: "hello".into() },
            ReadMsgEntry { sender: "bob".into(), content: "world".into() },
        ],
    };
    let md = action.to_markdown();

    // Vec section title
    assert!(md.contains("### 收到的消息 ###"), "should have vec section title");

    // Each struct element outputs its struct-level header as separator
    let header_count = md.matches("消息").count();
    // "消息" appears in: "收到的消息" (1x) + each element's struct header (2x) = at least 3
    assert!(header_count >= 3, "struct-level header should appear for each element, got {}", header_count);

    // Element fields rendered with @render titles
    assert!(md.contains("发送者: alice"), "first element sender");
    assert!(md.contains("内容: hello"), "first element content");
    assert!(md.contains("发送者: bob"), "second element sender");
    assert!(md.contains("内容: world"), "second element content");
}

#[test]
fn test_vec_struct_without_header() {
    // Message struct has NO struct-level doc comment
    // Elements should render fields directly without header
    let chat = Chat {
        messages: vec![
            Message { sender: "alice".into(), content: "hi".into() },
        ],
    };
    let md = chat.to_markdown();

    // No struct-level header for Message elements
    // Only the Vec field title appears as heading
    let heading_count = md.matches("###").count();
    assert_eq!(heading_count, 2, "only vec field title should have ### headings (open+close)");
}

// ============================================================
// P2 tests: Option<Vec<T>> support
// ============================================================

#[derive(Promptable)]
struct OptionalMessages {
    /// @render 消息列表
    entries: Option<Vec<ReadMsgEntry>>,
}

#[test]
fn test_option_vec_some() {
    let data = OptionalMessages {
        entries: Some(vec![
            ReadMsgEntry { sender: "alice".into(), content: "hello".into() },
            ReadMsgEntry { sender: "bob".into(), content: "world".into() },
        ]),
    };
    let md = data.to_markdown();

    // Section title from the field's @render doc
    assert!(md.contains("### 消息列表 ###"), "should have section title");

    // Each struct element outputs its struct-level header
    assert!(md.contains("发送者: alice"), "first element sender");
    assert!(md.contains("内容: hello"), "first element content");
    assert!(md.contains("发送者: bob"), "second element sender");
    assert!(md.contains("内容: world"), "second element content");
}

#[test]
fn test_option_vec_none() {
    let data = OptionalMessages {
        entries: None,
    };
    let md = data.to_markdown();

    // None → skip entirely
    assert!(!md.contains("消息列表"), "None Option<Vec> should be skipped");
    assert!(!md.contains("###"), "no headings for None");
}

#[test]
fn test_option_vec_empty() {
    let data = OptionalMessages {
        entries: Some(vec![]),
    };
    let md = data.to_markdown();

    // Some(empty vec) → skip (empty check in generated code)
    assert!(!md.contains("消息列表"), "empty Option<Vec> should be skipped");
}

// ============================================================
// Chrono date type rendering tests
// ============================================================

#[derive(Promptable)]
struct DateTimeStruct {
    /// @render 当前时间
    current_time: chrono::DateTime<chrono::Local>,
    /// @render 创建日期
    created: chrono::NaiveDate,
}

#[test]
fn test_chrono_datetime_default_format() {
    let dt = chrono::Local::now();
    let data = DateTimeStruct {
        current_time: dt,
        created: chrono::NaiveDate::from_ymd_opt(2026, 3, 24).unwrap(),
    };
    let md = data.to_markdown();

    // DateTime renders as inline with default format %Y%m%d%H%M%S
    assert!(md.contains("当前时间: "), "should have inline title for DateTime");
    assert!(md.contains(&dt.format("%Y%m%d%H%M%S").to_string()), "should contain formatted datetime");

    // NaiveDate renders as inline with default format %Y%m%d
    assert!(md.contains("创建日期: 20260324"), "should have inline NaiveDate");
}

#[derive(Promptable)]
struct CustomDateFormat {
    #[prompt(date_format = "%Y-%m-%d %H:%M")]
    /// @render 时间戳
    ts: chrono::NaiveDateTime,
}

#[test]
fn test_chrono_custom_date_format() {
    let ndt = chrono::NaiveDate::from_ymd_opt(2026, 3, 24).unwrap()
        .and_hms_opt(14, 30, 0).unwrap();
    let data = CustomDateFormat { ts: ndt };
    let md = data.to_markdown();

    assert!(md.contains("时间戳: 2026-03-24 14:30"), "should use custom format");
    assert!(!md.contains("20260324143000"), "should NOT use default format");
}

// ============================================================
// $size variable tests
// ============================================================

#[derive(Promptable)]
struct VecWithSize {
    /// @render 消息列表（size=$size）
    messages: Vec<String>,
}

#[test]
fn test_size_var_vec_string() {
    let data = VecWithSize {
        messages: vec!["hello".into(), "world".into()],
    };
    let md = data.to_markdown();

    // $size should be replaced with the rendered content length
    assert!(!md.contains("$size"), "should NOT contain raw $size variable");
    // Title should contain size= followed by a number
    assert!(md.contains("size="), "should contain size= in title");
    // Content is "hello\n\nworld\n" = 13 bytes
    assert!(md.contains("size=13"), "content length should be 13");
}

#[test]
fn test_size_var_vec_empty() {
    let data = VecWithSize {
        messages: vec![],
    };
    let md = data.to_markdown();

    // Empty vec → skip entirely
    assert!(!md.contains("消息列表"), "empty vec should be skipped");
    assert!(!md.contains("$size"), "no $size for empty vec");
}

#[derive(Promptable)]
#[allow(dead_code)]
struct InnerData {
    /// @render 名称
    name: String,
    /// @render 值
    value: String,
}

#[derive(Promptable)]
struct NestedWithSize {
    /// @render 详情（size=$size）
    detail: InnerData,
}

#[test]
fn test_size_var_other_struct() {
    let data = NestedWithSize {
        detail: InnerData {
            name: "test".into(),
            value: "hello world\nsecond line".into(),
        },
    };
    let md = data.to_markdown();

    assert!(md.contains("详情（size="), "should have size prefix for Other");
    assert!(!md.contains("$size"), "should NOT contain raw $size variable");
}

// ============================================================
// Volatile / CachedPrompt tests
// ============================================================

#[derive(Promptable)]
#[allow(dead_code)]
struct NoVolatile {
    /// @render 知识
    knowledge: String,
    /// @render 动作
    actions: String,
}

#[test]
fn test_cached_no_volatile() {
    let data = NoVolatile {
        knowledge: "some knowledge".into(),
        actions: "some actions".into(),
    };
    let cached = data.to_markdown_cached();
    // No volatile → everything in stable, delta empty
    assert!(!cached.stable.is_empty(), "stable should have content");
    assert!(cached.delta.is_empty(), "delta should be empty without volatile");
    assert!(!cached.has_volatile());
    // merge should equal to_markdown
    assert_eq!(cached.merge(), data.to_markdown());
}

#[derive(Promptable)]
#[allow(dead_code)]
struct WithVolatile {
    /// @render 知识
    knowledge: String,
    /// @render 历史动作
    actions: String,
    #[prompt(volatile)]
    /// @render 当前输入
    new_input: String,
}

#[test]
fn test_cached_with_volatile() {
    let data = WithVolatile {
        knowledge: "system knowledge".into(),
        actions: "action history".into(),
        new_input: "latest input".into(),
    };
    let cached = data.to_markdown_cached();
    // volatile present → stable has knowledge+actions, delta has new_input
    assert!(cached.has_volatile());
    assert!(cached.stable.contains("system knowledge"), "stable should contain knowledge");
    assert!(cached.stable.contains("action history"), "stable should contain actions");
    assert!(!cached.stable.contains("latest input"), "stable should NOT contain new_input");
    assert!(cached.delta.contains("latest input"), "delta should contain new_input");
    assert!(!cached.delta.contains("system knowledge"), "delta should NOT contain knowledge");
    // merge should equal to_markdown
    assert_eq!(cached.merge(), data.to_markdown());
}

#[derive(Promptable)]
#[allow(dead_code)]
struct VolatileMiddle {
    /// @render 系统提示
    system: String,
    #[prompt(volatile)]
    /// @render 用户消息
    user_msg: String,
    /// @render 附加信息
    extra: String,
}

#[test]
fn test_cached_volatile_middle() {
    let data = VolatileMiddle {
        system: "you are helpful".into(),
        user_msg: "hello".into(),
        extra: "extra info".into(),
    };
    let cached = data.to_markdown_cached();
    assert!(cached.has_volatile());
    // system is before volatile → in stable
    assert!(cached.stable.contains("you are helpful"));
    // user_msg and extra are at/after volatile → in delta
    assert!(cached.delta.contains("hello"));
    assert!(cached.delta.contains("extra info"));
    assert!(!cached.stable.contains("hello"));
    // merge == to_markdown
    assert_eq!(cached.merge(), data.to_markdown());
}

#[derive(Promptable)]
#[allow(dead_code)]
struct VolatileFirst {
    #[prompt(volatile)]
    /// @render 全部易变
    everything: String,
}

#[test]
fn test_cached_volatile_first_field() {
    let data = VolatileFirst {
        everything: "all volatile".into(),
    };
    let cached = data.to_markdown_cached();
    assert!(cached.has_volatile());
    // volatile on first field → stable is empty (just header if any), delta has everything
    assert!(cached.delta.contains("all volatile"));
    assert_eq!(cached.merge(), data.to_markdown());
}

// --- prompt(format) tests ---

#[derive(Promptable)]
#[prompt(format = "{timestamp}_{short_id}")]
struct ActionId {
    timestamp: String,
    short_id: String,
}

#[test]
fn test_format_basic() {
    let id = ActionId {
        timestamp: "20260326060745".to_string(),
        short_id: "dfb791".to_string(),
    };
    assert_eq!(id.to_markdown_depth(1), "20260326060745_dfb791");
    assert_eq!(id.to_markdown_item(), "20260326060745_dfb791");
}

#[derive(Promptable)]
#[prompt(format = "{a}-{b}/{c}")]
struct MultiField {
    a: String,
    b: String,
    c: String,
}

#[test]
fn test_format_multi_field_separators() {
    let m = MultiField {
        a: "x".to_string(),
        b: "y".to_string(),
        c: "z".to_string(),
    };
    assert_eq!(m.to_markdown_depth(1), "x-y/z");
}

#[derive(Promptable)]
struct OuterWithFormatField {
    /// action_id
    action_id: ActionId,
    /// name
    name: String,
}

#[test]
fn test_format_as_nested_field_inline() {
    let outer = OuterWithFormatField {
        action_id: ActionId {
            timestamp: "20260326".to_string(),
            short_id: "abc123".to_string(),
        },
        name: "test".to_string(),
    };
    let md = outer.to_markdown_depth(1);
    // ActionId renders as single line -> should be inline
    assert!(md.contains("action_id: 20260326_abc123\n"));
    assert!(md.contains("name: test\n"));
}

#[derive(Promptable)]
#[prompt(format = "{value}")]
struct SingleField {
    value: String,
}

#[test]
fn test_format_single_field() {
    let s = SingleField { value: "hello".to_string() };
    assert_eq!(s.to_markdown_depth(1), "hello");
}
