package main

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
	"time"
)

func testApplication(t *testing.T) *application {
	t.Helper()
	store, err := openPlaygroundStore(filepath.Join(t.TempDir(), "test.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { store.Close() })
	monaco := filepath.Join(t.TempDir(), "monaco")
	if err := os.MkdirAll(monaco, 0o755); err != nil {
		t.Fatal(err)
	}
	app, err := newApplication(store, map[string]memoryAsset{
		"core.js": {
			contentType: "text/javascript; charset=utf-8",
			content:     []byte("export const ready = true"),
			etag:        `"core"`,
			modified:    time.Unix(0, 0),
		},
	}, monaco)
	if err != nil {
		t.Fatal(err)
	}
	return app
}

func TestPackageIsServedFromMemory(t *testing.T) {
	request := httptest.NewRequest(http.MethodGet, "/pkg/core.js", nil)
	response := httptest.NewRecorder()
	testApplication(t).routes().ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("status = %d", response.Code)
	}
	if response.Body.String() != "export const ready = true" {
		t.Fatalf("body = %q", response.Body.String())
	}
	if response.Header().Get("Cross-Origin-Opener-Policy") != "same-origin" {
		t.Fatal("cross-origin isolation header is missing")
	}
	if response.Header().Get("Access-Control-Allow-Origin") != "*" {
		t.Fatal("package CORS header is missing")
	}
	if response.Header().Get("Cross-Origin-Resource-Policy") != "cross-origin" {
		t.Fatal("package resource policy is missing")
	}
}

func TestSaveAndLoadPlaygroundRevision(t *testing.T) {
	app := testApplication(t)
	handler := app.routes()
	requestBody, _ := json.Marshal(map[string]string{
		"title": "First scene",
		"code":  "const scene = true;",
	})
	request := httptest.NewRequest(http.MethodPost, "/api/playgrounds", bytes.NewReader(requestBody))
	response := httptest.NewRecorder()
	handler.ServeHTTP(response, request)
	if response.Code != http.StatusCreated {
		t.Fatalf("save status = %d: %s", response.Code, response.Body.String())
	}
	var saved playground
	if err := json.NewDecoder(response.Body).Decode(&saved); err != nil {
		t.Fatal(err)
	}
	if saved.Revision != 1 || !playgroundIDPattern.MatchString(saved.ID) {
		t.Fatalf("saved = %#v", saved)
	}
	requestBody, _ = json.Marshal(map[string]string{
		"id":    saved.ID,
		"title": "Second scene",
		"code":  "const scene = false;",
	})
	request = httptest.NewRequest(http.MethodPost, "/api/playgrounds", bytes.NewReader(requestBody))
	response = httptest.NewRecorder()
	handler.ServeHTTP(response, request)
	if response.Code != http.StatusCreated {
		t.Fatalf("second save status = %d: %s", response.Code, response.Body.String())
	}
	var second playground
	if err := json.NewDecoder(response.Body).Decode(&second); err != nil {
		t.Fatal(err)
	}
	if second.ID != saved.ID || second.Revision != 2 {
		t.Fatalf("second = %#v", second)
	}

	request = httptest.NewRequest(http.MethodGet, "/api/playgrounds/"+saved.ID+"/1", nil)
	response = httptest.NewRecorder()
	handler.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("load status = %d: %s", response.Code, response.Body.String())
	}
	var loaded playground
	if err := json.NewDecoder(response.Body).Decode(&loaded); err != nil {
		t.Fatal(err)
	}
	if loaded.Code != "const scene = true;" || loaded.Title != "First scene" {
		t.Fatalf("loaded = %#v", loaded)
	}

	request = httptest.NewRequest(http.MethodGet, "/api/playgrounds/"+saved.ID+"/2", nil)
	response = httptest.NewRecorder()
	handler.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("second load status = %d: %s", response.Code, response.Body.String())
	}
	if err := json.NewDecoder(response.Body).Decode(&loaded); err != nil {
		t.Fatal(err)
	}
	if loaded.Code != "const scene = false;" || loaded.Title != "Second scene" {
		t.Fatalf("second loaded = %#v", loaded)
	}
}

func TestSavedPlaygroundPageRoute(t *testing.T) {
	request := httptest.NewRequest(http.MethodGet, "/playground/abcdef12/1", nil)
	response := httptest.NewRecorder()
	testApplication(t).routes().ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("status = %d", response.Code)
	}
	if contentType := response.Header().Get("Content-Type"); contentType != "text/html; charset=utf-8" {
		t.Fatalf("content type = %q", contentType)
	}
}
