/*
 * Copyright (c) 2026-present, the Ladybird developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibCore/ImmutableBytes.h>
#include <LibHTTP/Cache/MemoryCache.h>
#include <LibHTTP/Cache/Utilities.h>
#include <LibHTTP/HeaderList.h>
#include <LibTest/TestCase.h>
#include <LibURL/Parser.h>

static URL::URL parse_url(StringView url)
{
    return URL::Parser::basic_parse(url).release_value();
}

static NonnullRefPtr<HTTP::HeaderList> create_cacheable_request_headers()
{
    return HTTP::HeaderList::create({
        { "Accept"sv, "*/*"sv },
    });
}

static NonnullRefPtr<HTTP::HeaderList> create_cacheable_response_headers()
{
    return HTTP::HeaderList::create({
        { "Cache-Control"sv, "max-age=60"sv },
    });
}

static NonnullRefPtr<HTTP::HeaderList> create_vary_user_agent_response_headers()
{
    return HTTP::HeaderList::create({
        { "Cache-Control"sv, "max-age=60"sv },
        { "Vary"sv, "User-Agent"sv },
    });
}

static Core::ImmutableBytes immutable_bytes(StringView bytes)
{
    return MUST(Core::ImmutableBytes::copy(bytes.bytes()));
}

TEST_CASE(javascript_bytecode_cache_round_trips_with_memory_cache_entry)
{
    auto cache = HTTP::MemoryCache::create();
    auto url = parse_url("https://example.com/script.js"sv);
    auto request_headers = create_cacheable_request_headers();
    auto response_headers = create_cacheable_response_headers();
    auto bytecode = immutable_bytes("cached bytecode"sv);

    cache->create_entry(url, "GET"sv, *request_headers, UnixDateTime::now(), 200, "OK"sv, *response_headers, bytecode, 0);
    cache->finalize_entry(url, "GET"sv, *request_headers, 200, *response_headers, immutable_bytes("console.log('hello');"sv));

    auto entry = cache->open_entry(url, "GET"sv, *request_headers, HTTP::CacheMode::Default);
    VERIFY(entry.has_value());
    VERIFY(entry->javascript_bytecode_cache.has_value());
    EXPECT_EQ(entry->javascript_bytecode_cache->bytes(), bytecode.bytes());
    EXPECT_EQ(entry->javascript_bytecode_cache_vary_key, Optional<u64> { 0 });
}

TEST_CASE(javascript_bytecode_cache_update_matches_memory_cache_vary_headers)
{
    auto cache = HTTP::MemoryCache::create();
    auto url = parse_url("https://example.com/script.js"sv);
    auto script_request_headers = create_cacheable_request_headers();
    auto cache_request_headers = HTTP::HeaderList::create({
        { "Accept"sv, "*/*"sv },
        { "User-Agent"sv, "Ladybird"sv },
    });
    auto response_headers = create_vary_user_agent_response_headers();
    auto bytecode = immutable_bytes("generated bytecode"sv);
    auto vary_key = HTTP::create_vary_key(*cache_request_headers, *response_headers);

    cache->create_entry(url, "GET"sv, *cache_request_headers, UnixDateTime::now(), 200, "OK"sv, *response_headers);
    cache->finalize_entry(url, "GET"sv, *cache_request_headers, 200, *response_headers, immutable_bytes("console.log('hello');"sv));

    cache->update_javascript_bytecode_cache(url, "GET"sv, *script_request_headers, vary_key, bytecode);

    auto entry = cache->open_entry(url, "GET"sv, *cache_request_headers, HTTP::CacheMode::Default);
    VERIFY(entry.has_value());
    EXPECT(!entry->javascript_bytecode_cache.has_value());
    EXPECT(!entry->javascript_bytecode_cache_vary_key.has_value());

    cache->update_javascript_bytecode_cache(url, "GET"sv, *cache_request_headers, vary_key, bytecode);

    entry = cache->open_entry(url, "GET"sv, *cache_request_headers, HTTP::CacheMode::Default);
    VERIFY(entry.has_value());
    VERIFY(entry->javascript_bytecode_cache.has_value());
    EXPECT_EQ(entry->javascript_bytecode_cache->bytes(), bytecode.bytes());
    EXPECT_EQ(entry->javascript_bytecode_cache_vary_key, Optional<u64> { vary_key });
}

TEST_CASE(javascript_bytecode_cache_can_be_added_after_memory_cache_entry_is_complete)
{
    auto cache = HTTP::MemoryCache::create();
    auto url = parse_url("https://example.com/script.js"sv);
    auto request_headers = create_cacheable_request_headers();
    auto response_headers = create_cacheable_response_headers();
    auto bytecode = immutable_bytes("generated bytecode"sv);

    cache->create_entry(url, "GET"sv, *request_headers, UnixDateTime::now(), 200, "OK"sv, *response_headers);
    cache->finalize_entry(url, "GET"sv, *request_headers, 200, *response_headers, immutable_bytes("console.log('hello');"sv));

    auto entry = cache->open_entry(url, "GET"sv, *request_headers, HTTP::CacheMode::Default);
    VERIFY(entry.has_value());
    EXPECT(!entry->javascript_bytecode_cache.has_value());
    EXPECT(!entry->javascript_bytecode_cache_vary_key.has_value());

    cache->update_javascript_bytecode_cache(url, "GET"sv, *request_headers, 0, bytecode);

    entry = cache->open_entry(url, "GET"sv, *request_headers, HTTP::CacheMode::Default);
    VERIFY(entry.has_value());
    VERIFY(entry->javascript_bytecode_cache.has_value());
    EXPECT_EQ(entry->javascript_bytecode_cache->bytes(), bytecode.bytes());
    EXPECT_EQ(entry->javascript_bytecode_cache_vary_key, Optional<u64> { 0 });
}
