// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: LicenseRef-MutuaL-1.2

package silverbullet

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

func newTestServer(mux *http.ServeMux) *httptest.Server {
	return httptest.NewServer(mux)
}

func testClient(url string) *Client {
	return New(url, Auth{})
}

func testClientWithBasicAuth(url string) *Client {
	return New(url, Auth{User: "testuser", Pass: "testpass"})
}

func setupAuthServer(mux *http.ServeMux) {
	mux.HandleFunc("/.auth", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		username := r.FormValue("username")
		password := r.FormValue("password")
		if username != "testuser" || password != "testpass" {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
		http.SetCookie(w, &http.Cookie{
			Name:  "auth_session",
			Value: "mock-jwt-token",
		})
		w.WriteHeader(http.StatusOK)
	})
}

func testClientWithBearerToken(url string) *Client {
	return New(url, Auth{Token: "testtoken123"})
}

func TestExecuteLua(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.Header.Get("Content-Type") != "text/plain" {
			t.Errorf("expected Content-Type text/plain, got %s", r.Header.Get("Content-Type"))
		}

		result := LuaResult{Result: json.RawMessage(`2`)}
		_ = json.NewEncoder(w).Encode(result)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	result, err := client.ExecuteLua(context.Background(), "1 + 1", 30)
	if err != nil {
		t.Fatalf("ExecuteLua failed: %v", err)
	}

	if result.Result == nil {
		t.Fatal("expected result, got nil")
	}
}

func TestExecuteLuaError(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = fmt.Fprintf(w, `{"error":"attempt to call nil value"}`)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	result, err := client.ExecuteLua(context.Background(), "bad()", 30)
	if err != nil {
		t.Fatalf("ExecuteLua returned unexpected error: %v", err)
	}

	if result.Error == "" {
		t.Fatal("expected error in result, got empty string")
	}
}

func TestExecuteLuaTimeoutHeader(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		timeout := r.Header.Get("X-Timeout")
		if timeout != "60" {
			t.Errorf("expected X-Timeout 60, got %s", timeout)
		}
		result := LuaResult{Result: json.RawMessage(`"ok"`)}
		_ = json.NewEncoder(w).Encode(result)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	_, err := client.ExecuteLua(context.Background(), "return 'ok'", 60)
	if err != nil {
		t.Fatalf("ExecuteLua failed: %v", err)
	}
}

func TestExecuteLuaTimeoutClamp(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		// Timeout should be clamped to 21600 even if caller passes 50000
		timeout := r.Header.Get("X-Timeout")
		if timeout != "21600" {
			t.Errorf("expected X-Timeout 21600, got %s", timeout)
		}
		result := LuaResult{Result: json.RawMessage(`"ok"`)}
		_ = json.NewEncoder(w).Encode(result)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	_, err := client.ExecuteLua(context.Background(), "return 'ok'", 50000)
	if err != nil {
		t.Fatalf("ExecuteLua failed: %v", err)
	}
}

func TestScreenshot(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/screenshot", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Errorf("expected GET, got %s", r.Method)
		}
		w.Header().Set("Content-Type", "image/png")
		_, _ = w.Write([]byte("fake-png-data"))
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	data, err := client.Screenshot(context.Background())
	if err != nil {
		t.Fatalf("Screenshot failed: %v", err)
	}

	if string(data) != "fake-png-data" {
		t.Errorf("expected fake-png-data, got %s", string(data))
	}
}

func TestConsoleLogs(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/logs", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Errorf("expected GET, got %s", r.Method)
		}
		limit := r.URL.Query().Get("limit")
		if limit != "5" {
			t.Errorf("expected limit=5, got %s", limit)
		}
		since := r.URL.Query().Get("since")
		if since != "1000" {
			t.Errorf("expected since=1000, got %s", since)
		}

		logs := LogsResult{
			Logs: []LogEntry{
				{Level: "log", Text: "Booting", Timestamp: 1710000000000},
				{Level: "info", Text: "Ready", Timestamp: 1710000000050},
			},
		}
		_ = json.NewEncoder(w).Encode(logs)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	result, err := client.ConsoleLogs(context.Background(), 5, 1000)
	if err != nil {
		t.Fatalf("ConsoleLogs failed: %v", err)
	}

	if len(result.Logs) != 2 {
		t.Fatalf("expected 2 log entries, got %d", len(result.Logs))
	}

	if result.Logs[0].Text != "Booting" {
		t.Errorf("expected first log text 'Booting', got %s", result.Logs[0].Text)
	}
}

func TestConsoleLogsDefaultLimit(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/logs", func(w http.ResponseWriter, r *http.Request) {
		limit := r.URL.Query().Get("limit")
		if limit != "100" {
			t.Errorf("expected default limit=100, got %s", limit)
		}
		logs := LogsResult{Logs: []LogEntry{}}
		_ = json.NewEncoder(w).Encode(logs)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClient(srv.URL)
	_, err := client.ConsoleLogs(context.Background(), 0, 0)
	if err != nil {
		t.Fatalf("ConsoleLogs failed: %v", err)
	}
}

func TestBasicAuth(t *testing.T) {
	mux := http.NewServeMux()
	setupAuthServer(mux)
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		// Verify session cookie is present
		cookie, err := r.Cookie("auth_session")
		if err != nil {
			t.Error("expected auth_session cookie, got none")
		}
		if cookie != nil && cookie.Value != "mock-jwt-token" {
			t.Errorf("expected cookie value 'mock-jwt-token', got %s", cookie.Value)
		}

		if r.Header.Get("X-Timeout") == "" {
			t.Error("expected X-Timeout header to be set")
		}

		result := LuaResult{Result: json.RawMessage(`"ok"`)}
		_ = json.NewEncoder(w).Encode(result)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClientWithBasicAuth(srv.URL)
	_, err := client.ExecuteLua(context.Background(), "return 'ok'", 30)
	if err != nil {
		t.Fatalf("ExecuteLua with basic auth failed: %v", err)
	}
}

func TestBearerToken(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		auth := r.Header.Get("Authorization")
		if auth != "Bearer testtoken123" {
			t.Errorf("expected Bearer token, got %s", auth)
		}
		result := LuaResult{Result: json.RawMessage(`"ok"`)}
		_ = json.NewEncoder(w).Encode(result)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := testClientWithBearerToken(srv.URL)
	_, err := client.ExecuteLua(context.Background(), "return 'ok'", 30)
	if err != nil {
		t.Fatalf("ExecuteLua with bearer token failed: %v", err)
	}
}

func TestBothAuth(t *testing.T) {
	mux := http.NewServeMux()
	setupAuthServer(mux)
	mux.HandleFunc("/.runtime/lua_script", func(w http.ResponseWriter, r *http.Request) {
		// Bearer token on Authorization header
		auth := r.Header.Get("Authorization")
		if auth != "Bearer testtoken123" {
			t.Errorf("expected Bearer token on Authorization, got %s", auth)
		}
		// Session cookie from /.auth login
		cookie, err := r.Cookie("auth_session")
		if err != nil {
			t.Error("expected auth_session cookie, got none")
		}
		if cookie != nil && cookie.Value != "mock-jwt-token" {
			t.Errorf("expected cookie value 'mock-jwt-token', got %s", cookie.Value)
		}
		result := LuaResult{Result: json.RawMessage(`"ok"`)}
		_ = json.NewEncoder(w).Encode(result)
	})

	srv := newTestServer(mux)
	defer srv.Close()

	client := New(srv.URL, Auth{User: "testuser", Pass: "testpass", Token: "testtoken123"})
	_, err := client.ExecuteLua(context.Background(), "return 'ok'", 30)
	if err != nil {
		t.Fatalf("ExecuteLua with both auth types failed: %v", err)
	}
}

func TestTrailingSlashURL(t *testing.T) {
	// Verify that trailing slashes in the base URL are handled correctly
	client := New("http://localhost:3000/", Auth{})
	if client.baseURL != "http://localhost:3000" {
		t.Errorf("expected trailing slash stripped, got %s", client.baseURL)
	}

	endpoint, err := client.resolveURL("/.runtime/lua_script")
	if err != nil {
		t.Fatalf("resolveURL failed: %v", err)
	}
	if endpoint != "http://localhost:3000/.runtime/lua_script" {
		t.Errorf("expected no double slash, got %s", endpoint)
	}
}
