# Getting started --------------------------------------------------------

test_that("can make simple request", {
  chat <- chat_snowflake("Be as terse as possible; no punctuation")
  resp <- chat$chat("What is 1 + 1?", echo = FALSE)
  expect_match(resp, "2")
  expect_equal(unname(chat$last_turn()@tokens[1:2] > 0), c(TRUE, TRUE))
})

test_that("can make simple streaming request", {
  chat <- chat_snowflake("Be as terse as possible; no punctuation")
  resp <- coro::collect(chat$stream("What is 1 + 1?"))
  expect_match(paste0(unlist(resp), collapse = ""), "2")
  expect_equal(unname(chat$last_turn()@tokens[1:2] > 0), c(TRUE, TRUE))
})

# Common provider interface -----------------------------------------------

test_that("defaults are reported", {
  # Setting a dummy account ensures we don't skip this test, even if there are
  # no Snowflake credentials available.
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  expect_snapshot(. <- chat_snowflake())
})

test_that("supports tool calling", {
  test_tools_simple(chat_snowflake)
})

test_that("can extract data", {
  test_data_extraction(chat_snowflake)
})

test_that("can use images", {
  # Snowflake models don't support images.
  #
  # test_images_inline(chat_snowflake)
  # test_images_remote(chat_snowflake)
})

# Auth --------------------------------------------------------------------

test_snowflake_key <- "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCbxG4OC5HU9QlK
dmtbQCa7r+uoKyDSisxqJQchfkDy64v6V6WsovI8evUGPQpkAbqsmXY3DR3T/Mco
P2oHyzGsfd2t7v6NLHNtGbMiEJYjVJvOw52Yn1m4WH5bEtl5JP/8W2qyTdr6qym+
m47X8hAqb+ToQjnolRq9xlme9n6vhwGi8mlco5POLCcEDhcYiqcPxI/WRqHDcdi8
/nU1eRhGTSe77NUnC0QQOojjRZ3P59NuA7zpgFdMLdE0I5qrfL3e6SQlnmTizdng
qiZUAI5p1ISZffn1FLf9GZlnD/usG0Dbp2MDGbYMhx8a5ii0RGyEINYrATdxkKaW
/AKGeKbdAgMBAAECggEAH+a022+HKGQeyP9DsWaMCDhZPRHIIRaIEt0Ofs+KobWX
72dv6NFeZwCPmf16WUz5XEv5qACpsTa92wJRxtLYk4kbk3m07FjEMv3mb/2Roh67
4jax2gYYq+aDykcr/uGTA639RhMn29qeLAlT0eojYW2VJfQaRAX1ehRbWnEFNRFR
Pyy1pBOCReDG0yyw1OtDv85H09UdiRWyNC7HkxdaMZns//GOQ3MwuZL61st7Aezg
Xz+mGw7v+SEIL0zk92GSIOHA0TXiUAhIWGxIyqSeNqw+Cl0+4r6ZuT+z2lILPR9C
UPVMtXUzUBhBPhtvPpq2RoRqcHzXWsdUcfteKyN/iQKBgQC5JvzZOwOnBcqaTUpn
ykrYwyiAOk0h3uOs4Mrs7A40xWmQ35VOb1gWVnTgvC91SBBfP/jGf02ZdLk5NG1/
oe13aKvQ6mh/jTImPLEPxsMm+469+nklitHwF8b6R3zrSPHoqdF4XUOHOcbK5V8W
MgUIIXDGtLCqxTns41VbIM9/5wKBgQDXXvgG5238F1LtHFG0FNZRilRt3d+cO1CU
HctSPGRXVe8ZGEJZ4F/TV6pWEOrdsuk5bp/IoDGKE2b9FI6K9BKy3Xc8qx5Um9zF
q5ca671UmZkcqu8jh99JSn9sKM7PP9QZInhP1eca7J9r2lhROHk0hsyTWtzuVcWO
JttBO0lamwKBgQCJWFGCNxO7h0FGewUxvs8MwqA9loH3GScc69e8LlNPdA2eKSzR
dSkL0PB8cTxnLKDwdzzsyixfJEXuGGUNo6nKxTuHCwufarcDxEu4H0JOnZbCeJX7
cmHPT2QL7pHM21yPscEwH0bjfcloYwPJLCutX1kQHaNb2lfg0LZVlh42iwKBgQCW
3yp0+66qiFRJUitSMb6pRHQ8us8ojMy31d9W7oOEQujJ9ZqVh37ZeHIU9KjzQZ/r
4bkBPGc3yLu+0qXAZZarwkUDNQR8VOtldfzWmQn6t9bwpDX99/LNTujQhg3KVXZp
XSJXGwtYayaK0VxJGXye9UdeeqqGM4O/Py0dF0EdvQKBgDo82ImF2mKzJUEBK33r
uGtR8Fxbg4cNRAc0W6xME86IVTnLnqLp1yeTZZGCFek6hDqERLCbQhQk8t1Szm0V
OdYSh6YfkxhsBGp6hHefOTWuoto4zHZ98uuu0GD8NkzGmnZApZ7It1MiH+SZPG9w
AK4HbizZMWlkvg87OphvnQhC
-----END PRIVATE KEY-----
"

test_that("Snowflake keypair token caching works as expected", {
  skip_if_not_installed("jose")

  # Random RSA key for testing.
  testkey <- openssl::read_key(test_snowflake_key)

  token1 <- snowflake_keypair_token("test1", "user", testkey)
  token2 <- snowflake_keypair_token("test2", "user", testkey)

  # Verify different tokens were returned
  expect_false(identical(token1, token2))

  # Verify cached tokens match original ones
  expect_identical(token1, snowflake_keypair_token("test1", "user", testkey))
  expect_identical(token2, snowflake_keypair_token("test2", "user", testkey))

  # Simulate a cache entry that has expired
  cache <- snowflake_keypair_cache("test1", testkey)
  creds_modified <- cache$get()
  creds_modified$expiry <- Sys.time() - 5
  cache$set(creds_modified)

  # Ensure the new token has been updated
  expect_false(
    identical(
      creds_modified,
      snowflake_keypair_token("test1", "user", testkey)
    )
  )
  expect_false(
    identical(token1, snowflake_keypair_token("test1", "user", testkey))
  )
  expect_false(
    identical(token2, snowflake_keypair_token("test1", "user", testkey))
  )
})

test_that("Snowflake OAuth tokens are detected correctly", {
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  credentials <- default_snowflake_credentials()
  expect_identical(
    credentials(),
    list(
      Authorization = "Bearer token",
      `X-Snowflake-Authorization-Token-Type` = "OAUTH"
    )
  )
})

test_that("Snowflake key-pair credentials are detected correctly", {
  skip_if_not_installed("jose")

  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_USER = "user",
    SNOWFLAKE_PRIVATE_KEY = test_snowflake_key
  )
  # Warm the cache so we can compare more easily.
  token <- snowflake_keypair_token(
    "testorg-test_account",
    "user",
    openssl::read_key(test_snowflake_key)
  )
  credentials <- default_snowflake_credentials()
  expect_identical(
    credentials(),
    list(
      Authorization = paste("Bearer", token),
      `X-Snowflake-Authorization-Token-Type` = "KEYPAIR_JWT"
    )
  )
})

test_that("snowflake account identifier is extracted from privatelink account and used in JWT claim", {
  skip_if_not_installed("jose")

  testkey <- openssl::read_key(test_snowflake_key)
  token <- snowflake_keypair_token(
    account = "mycompany.us-east-1.privatelink",
    user = "testuser",
    key = testkey
  )
  pubkey <- as.list(testkey)$pubkey
  decoded <- jose::jwt_decode_sig(token, pubkey = pubkey)
  expect_equal(decoded$sub, "MYCOMPANY.TESTUSER")
})

test_that("tokens can be requested from a Connect server", {
  skip_if_not_installed("connectcreds")

  withr::local_envvar(SNOWFLAKE_ACCOUNT = "testorg-test_account")
  connectcreds::local_mocked_connect_responses(token = "token")
  credentials <- default_snowflake_credentials()
  expect_identical(
    credentials(),
    list(
      Authorization = "Bearer token",
      `X-Snowflake-Authorization-Token-Type` = "OAUTH"
    )
  )
})

test_that("we can merge Snowflake's chunk format", {
  # Setting a dummy account ensures we don't skip this test, even if there are
  # no Snowflake credentials available.
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  chat <- chat_snowflake()
  provider <- chat$get_provider()
  chunk1 <- list(
    id = "id",
    model = "claude-3-5-sonnet",
    choices = list(
      list(
        delta = list(
          type = "text",
          content = "I",
          content_list = list(list(type = "text", text = "I")),
          text = "I"
        )
      )
    ),
    usage = structure(list(), names = character(0))
  )
  chunk2 <- list(
    id = "id",
    model = "claude-3-5-sonnet",
    choices = list(
      list(
        delta = list(
          type = "text",
          content = " aim to be direct and honest: I don't actually",
          content_list = list(
            list(
              type = "text",
              text = " aim to be direct and honest: I don't actually"
            )
          ),
          text = " aim to be direct and honest: I don't actually"
        )
      )
    ),
    usage = structure(list(), names = character(0))
  )
  expect_equal(
    stream_merge_chunks(
      provider,
      stream_merge_chunks(provider, NULL, chunk1),
      chunk2
    ),
    list(
      id = "id",
      model = "claude-3-5-sonnet",
      choices = list(
        list(
          message = list(
            content_list = list(
              list(
                type = "text",
                text = "I aim to be direct and honest: I don't actually"
              )
            )
          )
        )
      ),
      usage = structure(list(), names = character(0))
    )
  )
})

test_that("we can merge Snowflake's tool_use chunk format", {
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  chat <- chat_snowflake()
  provider <- chat$get_provider()

  # Simulate a response that starts with a tool call (no preceding text).
  # This is the scenario that triggers the bug in #938.
  chunk1 <- list(
    choices = list(
      list(
        delta = list(
          type = "tool_use",
          tool_use_id = "toolu_123",
          name = "my_tool",
          content_list = list(
            list(tool_use_id = "toolu_123", name = "my_tool")
          ),
          text = ""
        )
      )
    ),
    usage = structure(list(), names = character(0))
  )
  chunk2 <- list(
    choices = list(
      list(
        delta = list(
          type = "tool_use",
          input = "{\"x\":",
          content_list = list(list(input = "{\"x\":")),
          text = ""
        )
      )
    ),
    usage = structure(list(), names = character(0))
  )
  chunk3 <- list(
    choices = list(
      list(
        delta = list(
          type = "tool_use",
          input = " 5}",
          content_list = list(list(input = " 5}")),
          text = ""
        )
      )
    ),
    usage = structure(list(), names = character(0))
  )
  chunk4 <- list(
    choices = list(
      list(
        delta = list(
          type = "text",
          content_list = list(list(type = "text", text = "")),
          text = ""
        )
      )
    ),
    usage = list(prompt_tokens = 10, completion_tokens = 5)
  )

  result <- stream_merge_chunks(provider, NULL, chunk1)
  result <- stream_merge_chunks(provider, result, chunk2)
  result <- stream_merge_chunks(provider, result, chunk3)
  result <- stream_merge_chunks(provider, result, chunk4)

  expect_equal(
    result$choices[[1]]$message$content_list,
    list(
      list(
        type = "tool_use",
        tool_use_id = "toolu_123",
        name = "my_tool",
        input = "{\"x\": 5}"
      ),
      list(type = "text", text = "")
    )
  )

  # value_turn filters empty text and produces only the tool request
  turn <- value_turn(provider, result)
  expect_length(turn@contents, 1)
  expect_s3_class(turn@contents[[1]], "ellmer::ContentToolRequest")
  expect_equal(turn@contents[[1]]@id, "toolu_123")
  expect_equal(turn@contents[[1]]@name, "my_tool")
  expect_equal(turn@contents[[1]]@arguments, list(x = 5))
})

test_that("stream_content extracts text from Snowflake's delta format", {
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  chat <- chat_snowflake()
  provider <- chat$get_provider()

  # Text chunk with content yields ContentText
  text_event <- list(
    choices = list(
      list(
        delta = list(type = "text", content = "hello", text = "hello")
      )
    )
  )
  result <- stream_content(provider, text_event)
  expect_s3_class(result, "ellmer::ContentText")
  expect_equal(result@text, "hello")

  # Text chunk using delta$text (no delta$content)
  text_event2 <- list(
    choices = list(
      list(
        delta = list(type = "text", text = "world")
      )
    )
  )
  result2 <- stream_content(provider, text_event2)
  expect_s3_class(result2, "ellmer::ContentText")
  expect_equal(result2@text, "world")

  # Tool_use chunk returns NULL (not text)
  tool_event <- list(
    choices = list(
      list(
        delta = list(type = "tool_use", tool_use_id = "id", name = "fn")
      )
    )
  )
  expect_null(stream_content(provider, tool_event))

  # Empty text chunk returns NULL
  empty_event <- list(
    choices = list(
      list(
        delta = list(type = "text", text = "")
      )
    )
  )
  expect_null(stream_content(provider, empty_event))
})

test_that("as_json handles tool-only assistant turns", {
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  chat <- chat_snowflake()
  provider <- chat$get_provider()

  # An assistant turn with only a tool request (no preceding text)
  turn <- Turn(
    "assistant",
    contents = list(
      ContentToolRequest("toolu_123", "my_tool", list(x = 5))
    )
  )
  result <- as_json(provider, turn)
  expect_equal(result$role, "assistant")
  expect_equal(result$content_list[[1]]$type, "tool_use")
  expect_equal(result$content_list[[1]]$tool_use$tool_use_id, "toolu_123")
})

test_that("chat_snowflake() supports parameters", {
  # Setting a dummy account ensures we don't skip this test, even if there are
  # no Snowflake credentials available.
  withr::local_envvar(
    SNOWFLAKE_ACCOUNT = "testorg-test_account",
    SNOWFLAKE_TOKEN = "token"
  )
  chat <- chat_snowflake(
    model = "claude-3-5-sonnet",
    params = params(
      temperature = 0.7,
      max_tokens = 100,
      top_p = 0.9,
      # This is a Snowflake-specific parameter.
      guardrails = list(
        enabled = TRUE
      )
    )
  )
  provider <- chat$get_provider()
  expect_equal(
    chat_body(provider),
    list(
      model = "claude-3-5-sonnet",
      temperature = 0.7,
      top_p = 0.9,
      max_tokens = 100,
      guardrails = list(enabled = TRUE),
      stream = TRUE
    )
  )
  # Warn/ignore unsupported parameters.
  provider@params <- params(seed = 1L)
  expect_warning(
    out <- chat_body(provider),
    regexp = "unsupported parameters"
  )
  expect_equal(out, list(model = "claude-3-5-sonnet", stream = TRUE))
})
