package sources

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/salvacybersec/keyhunter/pkg/providers"
	"github.com/salvacybersec/keyhunter/pkg/recon"
)

func bitbucketTestRegistry() *providers.Registry {
	return providers.NewRegistryFromProviders([]providers.Provider{
		{Name: "openai", Keywords: []string{"sk-proj-"}},
	})
}

func newBitbucketSource(baseURL, token, workspace string) *BitbucketSource {
	return &BitbucketSource{
		Token:     token,
		Workspace: workspace,
		BaseURL:   baseURL,
		Registry:  bitbucketTestRegistry(),
		Limiters:  recon.NewLimiterRegistry(),
	}
}

func TestBitbucket_EnabledRequiresTokenAndWorkspace(t *testing.T) {
	cfg := recon.Config{}

	if newBitbucketSource("", "", "").Enabled(cfg) {
		t.Fatal("expected disabled when token+workspace empty")
	}
	if newBitbucketSource("", "tok", "").Enabled(cfg) {
		t.Fatal("expected disabled when workspace empty")
	}
	if newBitbucketSource("", "", "ws").Enabled(cfg) {
		t.Fatal("expected disabled when token empty")
	}
	if !newBitbucketSource("", "tok", "ws").Enabled(cfg) {
		t.Fatal("expected enabled when both set")
	}
}

func TestBitbucket_SweepEmitsFindings(t *testing.T) {
	var gotAuth, gotPath string
	var gotQuery string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotAuth = r.Header.Get("Authorization")
		gotPath = r.URL.Path
		gotQuery = r.URL.Query().Get("search_query")
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{
			"values": [
				{
					"content_match_count": 2,
					"file": {"path": "secrets/.env", "commit": {"hash": "deadbeef"}},
					"page_url": "https://bitbucket.org/testws/repo/src/deadbeef/secrets/.env"
				}
			]
		}`))
	}))
	t.Cleanup(srv.Close)

	src := newBitbucketSource(srv.URL, "tok", "testws")
	out := make(chan recon.Finding, 16)
	if err := src.Sweep(context.Background(), "", out); err != nil {
		t.Fatalf("Sweep: %v", err)
	}
	close(out)

	if gotAuth != "Bearer tok" {
		t.Errorf("Authorization header = %q, want Bearer tok", gotAuth)
	}
	if gotPath != "/2.0/workspaces/testws/search/code" {
		t.Errorf("path = %q", gotPath)
	}
	if gotQuery == "" {
		t.Errorf("expected search_query param to be set")
	}

	var findings []recon.Finding
	for f := range out {
		findings = append(findings, f)
	}
	if len(findings) == 0 {
		t.Fatal("expected at least 1 finding")
	}
	f := findings[0]
	if f.SourceType != "recon:bitbucket" {
		t.Errorf("SourceType = %q", f.SourceType)
	}
	if !strings.Contains(f.Source, "bitbucket.org/testws/repo") {
		t.Errorf("Source = %q", f.Source)
	}
}

func TestBitbucket_Unauthorized(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "nope", http.StatusUnauthorized)
	}))
	t.Cleanup(srv.Close)

	src := newBitbucketSource(srv.URL, "tok", "testws")
	out := make(chan recon.Finding, 4)
	err := src.Sweep(context.Background(), "", out)
	if !errors.Is(err, ErrUnauthorized) {
		t.Fatalf("err = %v, want ErrUnauthorized", err)
	}
}

func TestBitbucket_ContextCancellation(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(2 * time.Second)
		w.WriteHeader(200)
		_, _ = w.Write([]byte(`{"values":[]}`))
	}))
	t.Cleanup(srv.Close)

	src := newBitbucketSource(srv.URL, "tok", "testws")
	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel()
	out := make(chan recon.Finding, 1)
	err := src.Sweep(ctx, "", out)
	if err == nil {
		t.Fatal("expected error from cancelled context")
	}
}
