package changelog

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

func TestSanitizeSubject(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name    string
		subject string
		want    string
	}{
		{
			name:    "merge prefix with quotes",
			subject: "Merge pull request 'Add classes overhaul (#123)'",
			want:    "Add classes overhaul",
		},
		{
			name:    "plain subject",
			subject: "Add classes overhaul (#123)",
			want:    "Add classes overhaul",
		},
		{
			name:    "double quotes",
			subject: `Merge pull request "Add classes overhaul (#123)"`,
			want:    "Add classes overhaul",
		},
		{
			name:    "direct push",
			subject: "Fix direct push handling",
			want:    "Fix direct push handling",
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			if got := sanitizeSubject(tt.subject); got != tt.want {
				t.Fatalf("sanitizeSubject(%q) = %q, want %q", tt.subject, got, tt.want)
			}
		})
	}
}

func TestShortCommitHash(t *testing.T) {
	t.Parallel()

	if got := shortCommitHash("1234567890abcdef"); got != "1234567" {
		t.Fatalf("shortCommitHash() = %q, want %q", got, "1234567")
	}
	if got := shortCommitHash("1234567"); got != "1234567" {
		t.Fatalf("shortCommitHash() preserved %q unexpectedly as %q", "1234567", got)
	}
}

func TestOwnerAndRepoFromURL(t *testing.T) {
	t.Parallel()

	owner, repo, err := ownerAndRepoFromURL("https://gitea.example.test/org/repo.git")
	if err != nil {
		t.Fatalf("ownerAndRepoFromURL returned error: %v", err)
	}
	if owner != "org" || repo != "repo" {
		t.Fatalf("ownerAndRepoFromURL returned %q/%q", owner, repo)
	}
}

func TestDeriveAPIBaseURL(t *testing.T) {
	t.Parallel()

	got := deriveAPIBaseURL("https://gitea.example.test/org/repo")
	want := "https://gitea.example.test/api/v1"
	if got != want {
		t.Fatalf("deriveAPIBaseURL() = %q, want %q", got, want)
	}
}

func TestNormalizeAPIBaseURL(t *testing.T) {
	t.Parallel()

	tests := []struct {
		input string
		want  string
	}{
		{input: "https://gitea.example.test", want: "https://gitea.example.test/api/v1"},
		{input: "https://gitea.example.test/api/v1", want: "https://gitea.example.test/api/v1"},
	}

	for _, tt := range tests {
		if got := normalizeAPIBaseURL(tt.input); got != tt.want {
			t.Fatalf("normalizeAPIBaseURL(%q) = %q, want %q", tt.input, got, tt.want)
		}
	}
}

func TestGenerateIncludesPullRequestsAndDirectPushes(t *testing.T) {
	t.Parallel()

	repoRoot := t.TempDir()
	runGit(t, repoRoot, "init")
	runGit(t, repoRoot, "config", "user.name", "Test User")
	runGit(t, repoRoot, "config", "user.email", "test@example.com")

	writeFile(t, filepath.Join(repoRoot, "content", "entry.txt"), "initial\n")
	runGit(t, repoRoot, "add", ".")
	runGit(t, repoRoot, "commit", "-m", "Initial import")
	runGit(t, repoRoot, "tag", "v1.0.0")

	writeFile(t, filepath.Join(repoRoot, "content", "entry.txt"), "direct\n")
	runGit(t, repoRoot, "add", ".")
	runGit(t, repoRoot, "commit", "-m", "Fix direct push handling")
	directHash := strings.TrimSpace(runGit(t, repoRoot, "rev-parse", "HEAD"))

	writeFile(t, filepath.Join(repoRoot, "content", "entry.txt"), "pull\n")
	runGit(t, repoRoot, "add", ".")
	runGit(t, repoRoot, "commit", "-m", "Add release summary (#12)")
	runGit(t, repoRoot, "tag", "v1.1.0")

	configPath := filepath.Join(repoRoot, "scripts", "changelog.json")
	writeFile(t, configPath, `{
  "repo_url": "REPO_URL",
  "categories": [
    {
      "title": "Content",
      "sections": [
        {
          "title": "Entries",
          "paths": ["content/"]
        }
      ]
    }
  ]
}`)

	var pullRequests int
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/v1/repos/org/repo/pulls/12" {
			t.Fatalf("unexpected API path %q", r.URL.Path)
		}
		pullRequests++
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{"user":{"full_name":"Patch Author","login":"patch-author"}}`)
	}))
	defer server.Close()

	repoURL := server.URL + "/org/repo"
	configData, err := os.ReadFile(configPath)
	if err != nil {
		t.Fatalf("read changelog config: %v", err)
	}
	configData = bytes.ReplaceAll(configData, []byte("REPO_URL"), []byte(repoURL))
	if err := os.WriteFile(configPath, configData, 0o644); err != nil {
		t.Fatalf("write changelog config: %v", err)
	}

	var stdout bytes.Buffer
	err = Generate(Options{
		RepoRoot:    repoRoot,
		ConfigPath:  configPath,
		CurrentTag:  "v1.1.0",
		PreviousTag: "v1.0.0",
		APIBaseURL:  server.URL + "/api/v1",
		Stdout:      &stdout,
	})
	if err != nil {
		t.Fatalf("Generate() returned error: %v", err)
	}

	rendered := stdout.String()
	if !strings.Contains(rendered, "- Add release summary ([#12]("+repoURL+"/pulls/12)) - From Patch Author") {
		t.Fatalf("rendered changelog missing pull entry:\n%s", rendered)
	}
	if !strings.Contains(rendered, "- Fix direct push handling (["+shortCommitHash(directHash)+"]("+repoURL+"/commit/"+directHash+")) - From Test User") {
		t.Fatalf("rendered changelog missing direct-push entry:\n%s", rendered)
	}
	if pullRequests != 1 {
		t.Fatalf("expected 1 pull lookup, got %d", pullRequests)
	}
}

func runGit(t *testing.T, repoRoot string, args ...string) string {
	t.Helper()

	cmd := exec.Command("git", args...)
	cmd.Dir = repoRoot
	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %s failed: %v\n%s", strings.Join(args, " "), err, output)
	}
	return string(output)
}

func writeFile(t *testing.T, path string, content string) {
	t.Helper()

	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatalf("MkdirAll(%q): %v", path, err)
	}
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatalf("WriteFile(%q): %v", path, err)
	}
}
