package web

import (
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"git-browser/internal/repository"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/config"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
)

func TestServerRendersCorePages(t *testing.T) {
	root := t.TempDir()
	if err := initRepo(root, "demo"); err != nil {
		t.Fatal(err)
	}

	store, err := repository.Discover(root)
	if err != nil {
		t.Fatal(err)
	}

	server, err := NewServer(store, Config{
		SSHUser:   "git",
		CloneRoot: "repos",
	})
	if err != nil {
		t.Fatal(err)
	}

	repo, err := store.Open("demo")
	if err != nil {
		t.Fatal(err)
	}
	rev, err := repo.DefaultRevision()
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		path string
		want string
	}{
		{path: "/", want: "Repositories"},
		{path: "/demo", want: "git@localhost:repos/demo"},
		{path: "/demo", want: "data-copy-text=\"git@localhost:repos/demo\""},
		{path: "/demo", want: "revision: <strong>master</strong>"},
		{path: "/demo", want: "README.md"},
		{path: "/demo/blob/" + rev.Name + "/README.md", want: "aria-label=\"Breadcrumb\""},
		{path: "/demo/log/" + rev.Name, want: "/demo/tree/"},
		{path: "/demo/log/" + rev.Name + "?path=README.md", want: "back to file"},
		{path: "/demo/branches", want: "feature"},
		{path: "/demo/tree/feature/", want: "README.md"},
		{path: "/demo/tree/feature/", want: "/demo/branches?rev=feature"},
	}

	for _, tc := range tests {
		req := httptest.NewRequest(http.MethodGet, tc.path, nil)
		req.Host = "localhost:8080"
		rec := httptest.NewRecorder()
		server.Handler().ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Fatalf("%s: got status %d, want %d", tc.path, rec.Code, http.StatusOK)
		}
		if !strings.Contains(rec.Body.String(), tc.want) {
			t.Fatalf("%s: response did not contain %q\nbody:\n%s", tc.path, tc.want, rec.Body.String())
		}
	}
}

func TestServerServesRawBlob(t *testing.T) {
	root := t.TempDir()
	if err := initRepo(root, "demo"); err != nil {
		t.Fatal(err)
	}

	store, err := repository.Discover(root)
	if err != nil {
		t.Fatal(err)
	}

	server, err := NewServer(store, Config{
		SSHUser:   "git",
		CloneRoot: "repos",
	})
	if err != nil {
		t.Fatal(err)
	}

	repo, err := store.Open("demo")
	if err != nil {
		t.Fatal(err)
	}
	rev, err := repo.DefaultRevision()
	if err != nil {
		t.Fatal(err)
	}

	req := httptest.NewRequest(http.MethodGet, "/demo/blob/"+rev.Name+"/README.md?raw=1", nil)
	req.Host = "localhost:8080"
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
	}
	if body := rec.Body.String(); body != "hello\n" {
		t.Fatalf("got raw body %q, want %q", body, "hello\n")
	}
	if contentType := rec.Header().Get("Content-Type"); !strings.Contains(contentType, "text/plain") {
		t.Fatalf("got content type %q, want text/plain", contentType)
	}
}

func TestServerRendersEmptyRepositoryPage(t *testing.T) {
	root := t.TempDir()
	if _, err := git.PlainInit(filepath.Join(root, "empty"), false); err != nil {
		t.Fatal(err)
	}

	store, err := repository.Discover(root)
	if err != nil {
		t.Fatal(err)
	}

	server, err := NewServer(store, Config{
		SSHUser:   "git",
		CloneRoot: "",
	})
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		path string
		want string
	}{
		{path: "/empty", want: "This repository has no commits yet."},
		{path: "/empty/branches", want: "No branches found."},
	}

	for _, tc := range tests {
		req := httptest.NewRequest(http.MethodGet, tc.path, nil)
		req.Host = "localhost:8080"
		rec := httptest.NewRecorder()
		server.Handler().ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Fatalf("%s: got status %d, want %d", tc.path, rec.Code, http.StatusOK)
		}
		if !strings.Contains(rec.Body.String(), tc.want) {
			t.Fatalf("%s: response did not contain %q\nbody:\n%s", tc.path, tc.want, rec.Body.String())
		}
	}
}

func initRepo(root, name string) error {
	path := filepath.Join(root, name)
	repo, err := git.PlainInit(path, false)
	if err != nil {
		return err
	}

	if err := os.WriteFile(filepath.Join(path, "README.md"), []byte("hello\n"), 0o644); err != nil {
		return err
	}
	if err := os.Mkdir(filepath.Join(path, "src"), 0o755); err != nil {
		return err
	}
	if err := os.WriteFile(filepath.Join(path, "src", "main.go"), []byte("package main\n"), 0o644); err != nil {
		return err
	}

	wt, err := repo.Worktree()
	if err != nil {
		return err
	}
	if _, err := wt.Add("README.md"); err != nil {
		return err
	}
	if _, err := wt.Add("src/main.go"); err != nil {
		return err
	}

	hash, err := wt.Commit("initial commit", &git.CommitOptions{
		Author: &object.Signature{
			Name:  "Test",
			Email: "test@example.com",
			When:  time.Unix(0, 0),
		},
	})
	if err != nil {
		return err
	}

	if err := repo.CreateBranch(&config.Branch{Name: "feature"}); err != nil {
		return err
	}

	return repo.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName("feature"), hash))
}