use core::fmt::Write;
use core::ops::Range;

use super::*;
use crate::replacer::Writable;

#[test]
fn ansi_replacen_plain_single() {
    let s = "hello world hello";
    assert_eq!(s.ansi_replacen("hello", "hi", 1), "hi world hello");
}

#[test]
fn ansi_replacen_plain_all() {
    let s = "hello world hello";
    assert_eq!(s.ansi_replacen("hello", "hi", usize::MAX), "hi world hi");
}

#[test]
fn ansi_replacen_zero_count() {
    let s = "hello world";
    assert_eq!(s.ansi_replacen("hello", "bye", 0), "hello world");
}

#[test]
fn ansi_replacen_no_match() {
    let s = "hello world";
    assert_eq!(s.ansi_replacen("xyz", "abc", 5), "hello world");
}

#[test]
fn ansi_replacen_with_empty_replacement() {
    let s = "foo_bar_baz";
    assert_eq!(s.ansi_replacen("_", "", 1), "foobar_baz");
}

#[test]
fn ansi_replacen_skips_ansi_in_matching() {
    // Visible text is "hello world", ANSI wraps "hello"
    let s = "\x1b[32mhello\x1b[0m world";
    assert_eq!(s.ansi_replacen("hello", "hi", 1), "\x1b[32mhi\x1b[0m world");
}

#[test]
fn ansi_replacen_preserves_ansi_outside_match() {
    let s = "foo \x1b[1mbar\x1b[0m baz";
    assert_eq!(
        s.ansi_replacen("baz", "qux", 1),
        "foo \x1b[1mbar\x1b[0m qux"
    );
}

#[test]
fn ansi_replacen_match_spanning_ansi_boundary() {
    // Match spans across an ANSI code: visible text "helloworld" matches.
    // The ANSI code within the matched span is preserved in output.
    let s = "hello\x1b[31mworld";
    // The stripped result should be "replaced"
    assert_eq!(
        s.ansi_replacen("helloworld", "replaced", 1),
        "\x1b[31mreplaced"
    );
}

#[test]
fn ansi_replacen_multiple_matches_limited() {
    let s = "aXa\x1b[0mXa";
    // Visible: "aXaXa", replace "a" with "b", count=2
    assert_eq!(s.ansi_replacen("a", "b", 2), "bXb\x1b[0mXa");
}

#[test]
fn ansi_replacen_longer_replacement() {
    let s = "\x1b[32ma\x1b[0m b";
    // Visible: "a b", replace "a" with "xyz"
    assert_eq!(s.ansi_replacen("a", "xyz", 1), "\x1b[32mxyz\x1b[0m b");
}

#[test]
fn ansi_replacen_at_end() {
    let s = "hello\x1b[0m";
    assert_eq!(s.ansi_replacen("hello", "bye", 1), "bye\x1b[0m");
}

#[test]
fn ansi_replacen_empty_from() {
    // Matches Rust's str::replacen behavior: empty pattern matches between every char
    let s = "hello";
    assert_eq!(s.ansi_replacen("", "x", 5), "xhxexlxlxo");
}

#[test]
fn ansi_replacen_empty_string() {
    let s = "";
    assert_eq!(s.ansi_replacen("a", "b", 1), "");
}

#[test]
fn ansi_replacen_only_ansi() {
    let s = "\x1b[32m\x1b[0m";
    assert_eq!(s.ansi_replacen("a", "b", 1), "\x1b[32m\x1b[0m");
}

#[test]
fn ansi_replacen_adjacent_matches() {
    let s = "aaaa";
    assert_eq!(s.ansi_replacen("aa", "x", usize::MAX), "xx");
}

#[test]
fn ansi_replacen_replace_with_same_length() {
    let s = "\x1b[1mfoo\x1b[0m bar";
    assert_eq!(s.ansi_replacen("foo", "baz", 1), "\x1b[1mbaz\x1b[0m bar");
}

#[test]
fn ansi_replacen_multiple_ansi_between_match() {
    // Multiple ANSI codes interleaved within a single match
    let s = "\x1b[1mh\x1b[32me\x1b[0mllo";
    assert_eq!(
        s.ansi_replacen("hello", "world", 1),
        "\x1b[1m\x1b[32m\x1b[0mworld"
    );
}

#[test]
fn ansi_replacen_trailing_text_after_match() {
    let s = "abc\x1b[31mdef\x1b[0m ghi";
    // Visible: "abcdef ghi", replace "def" → "XY"
    assert_eq!(s.ansi_replacen("def", "XY", 1), "abc\x1b[31mXY\x1b[0m ghi");
}

#[test]
fn ansi_strip_basic() {
    let s = "\x1b[32mhello\x1b[0m world";
    assert_eq!(s.ansi_strip(), "hello world");
}

#[test]
fn ansi_strip_no_ansi() {
    assert_eq!("plain text".ansi_strip(), "plain text");
}

#[test]
fn ansi_strip_only_ansi() {
    assert_eq!("\x1b[32m\x1b[0m".ansi_strip(), "");
}

// Tests for ansi_replacen with a replacer function

#[test]
fn ansi_replacen_fn_uppercase() {
    let s = "hello world hello";
    let result = s.ansi_replacen(
        "hello",
        |matched: &str, _range: Range<usize>, dst: &mut Writable| {
            dst.write_str(&matched.to_uppercase())
        },
        usize::MAX,
    );
    assert_eq!(result, "HELLO world HELLO");
}

#[test]
fn ansi_replacen_fn_with_range() {
    let s = "aXbXc";
    let result = s.ansi_replacen(
        "X",
        |_matched: &str, range: Range<usize>, dst: &mut Writable| write!(dst, "[{}]", range.start),
        usize::MAX,
    );
    assert_eq!(result, "a[1]b[3]c");
}

#[test]
fn ansi_replacen_fn_limited_count() {
    let s = "aaaa";
    let result = s.ansi_replacen(
        "a",
        |_: &str, _: Range<usize>, dst: &mut Writable| dst.write_char('B'),
        2,
    );
    assert_eq!(result, "BBaa");
}

#[test]
fn ansi_replacen_fn_skips_ansi() {
    let s = "\x1b[32mhello\x1b[0m world";
    let result = s.ansi_replacen(
        "hello",
        |matched: &str, _: Range<usize>, dst: &mut Writable| dst.write_str(&matched.to_uppercase()),
        1,
    );
    assert_eq!(result, "\x1b[32mHELLO\x1b[0m world");
}

#[test]
fn ansi_replacen_fn_spanning_ansi() {
    let s = "hello\x1b[31mworld";
    let result = s.ansi_replacen(
        "helloworld",
        |_: &str, _: Range<usize>, dst: &mut Writable| dst.write_str("REPLACED"),
        1,
    );
    assert_eq!(result, "\x1b[31mREPLACED");
}

#[test]
fn ansi_replacen_fn_no_match() {
    let s = "hello world";
    let result = s.ansi_replacen(
        "xyz",
        |_: &str, _: Range<usize>, dst: &mut Writable| dst.write_str("BAD"),
        5,
    );
    assert_eq!(result, "hello world");
}

#[test]
fn ansi_replacen_fn_empty_replacement() {
    let s = "foo_bar_baz";
    let result = s.ansi_replacen(
        "_",
        |_: &str, _: Range<usize>, _dst: &mut Writable| {
            // push nothing — effectively removes the match
            Ok(())
        },
        usize::MAX,
    );
    assert_eq!(result, "foobarbaz");
}

#[test]
fn ansi_replacen_fn_stateful_counter() {
    let s = "a a a";
    let mut count = 0;
    let result = s.ansi_replacen(
        "a",
        |_: &str, _: Range<usize>, dst: &mut Writable| {
            count += 1;
            write!(dst, "{count}")
        },
        usize::MAX,
    );
    assert_eq!(result, "1 2 3");
    assert_eq!(count, 3);
}

#[test]
fn ansi_replacen_fn_preserves_ansi_outside_match() {
    let s = "foo \x1b[1mbar\x1b[0m baz";
    let result = s.ansi_replacen(
        "baz",
        |_: &str, _: Range<usize>, dst: &mut Writable| dst.write_str("QUX"),
        1,
    );
    assert_eq!(result, "foo \x1b[1mbar\x1b[0m QUX");
}

#[test]
fn ansi_replacen_fn_multiple_ansi_in_match() {
    let s = "\x1b[1mh\x1b[32me\x1b[0mllo";
    let result = s.ansi_replacen(
        "hello",
        |_: &str, _: Range<usize>, dst: &mut Writable| dst.write_str("world"),
        1,
    );
    assert_eq!(result, "\x1b[1m\x1b[32m\x1b[0mworld");
}
