package sources

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

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

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

func newGistSource(baseURL, token string) *GistSource {
	return &GistSource{
		Token:    token,
		BaseURL:  baseURL,
		Registry: gistTestRegistry(),
		Limiters: recon.NewLimiterRegistry(),
	}
}

func TestGist_EnabledRequiresToken(t *testing.T) {
	cfg := recon.Config{}
	if newGistSource("", "").Enabled(cfg) {
		t.Fatal("expected disabled when token empty")
	}
	if !newGistSource("", "tok").Enabled(cfg) {
		t.Fatal("expected enabled when token set")
	}
}

func TestGist_SweepEmitsFindingsOnKeywordMatch(t *testing.T) {
	var gotAuth, gotListPath string

	mux := http.NewServeMux()
	var srv *httptest.Server

	mux.HandleFunc("/gists/public", func(w http.ResponseWriter, r *http.Request) {
		gotAuth = r.Header.Get("Authorization")
		gotListPath = r.URL.Path
		w.Header().Set("Content-Type", "application/json")
		body := fmt.Sprintf(`[
			{
				"html_url": "https://gist.github.com/alice/aaa",
				"files": {
					"leak.env": {"filename": "leak.env", "raw_url": "%s/raw/aaa"}
				}
			},
			{
				"html_url": "https://gist.github.com/bob/bbb",
				"files": {
					"notes.md": {"filename": "notes.md", "raw_url": "%s/raw/bbb"}
				}
			}
		]`, srv.URL, srv.URL)
		_, _ = w.Write([]byte(body))
	})
	mux.HandleFunc("/raw/aaa", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("OPENAI_API_KEY=sk-proj-1234567890abcdefghijk"))
	})
	mux.HandleFunc("/raw/bbb", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("just some unrelated notes here"))
	})

	srv = httptest.NewServer(mux)
	t.Cleanup(srv.Close)

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

	if gotAuth != "Bearer tok" {
		t.Errorf("Authorization = %q", gotAuth)
	}
	if gotListPath != "/gists/public" {
		t.Errorf("list path = %q", gotListPath)
	}

	var findings []recon.Finding
	for f := range out {
		findings = append(findings, f)
	}
	if len(findings) != 1 {
		t.Fatalf("findings count = %d, want 1 (only aaa matches sk-proj-)", len(findings))
	}
	f := findings[0]
	if !strings.Contains(f.Source, "alice/aaa") {
		t.Errorf("Source = %q, want gist alice/aaa", f.Source)
	}
	if f.SourceType != "recon:gist" {
		t.Errorf("SourceType = %q", f.SourceType)
	}
	if f.ProviderName != "openai" {
		t.Errorf("ProviderName = %q, want openai", f.ProviderName)
	}
}

func TestGist_NoMatch_NoFinding(t *testing.T) {
	var srv *httptest.Server
	mux := http.NewServeMux()
	mux.HandleFunc("/gists/public", func(w http.ResponseWriter, r *http.Request) {
		body := fmt.Sprintf(`[{"html_url":"https://gist.github.com/x/y","files":{"a.txt":{"filename":"a.txt","raw_url":"%s/raw/x"}}}]`, srv.URL)
		_, _ = w.Write([]byte(body))
	})
	mux.HandleFunc("/raw/x", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("nothing interesting"))
	})
	srv = httptest.NewServer(mux)
	t.Cleanup(srv.Close)

	src := newGistSource(srv.URL, "tok")
	out := make(chan recon.Finding, 4)
	if err := src.Sweep(context.Background(), "", out); err != nil {
		t.Fatalf("Sweep: %v", err)
	}
	close(out)
	n := 0
	for range out {
		n++
	}
	if n != 0 {
		t.Fatalf("findings = %d, want 0", n)
	}
}

func TestGist_Unauthorized(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "bad", http.StatusUnauthorized)
	}))
	t.Cleanup(srv.Close)
	src := newGistSource(srv.URL, "tok")
	out := make(chan recon.Finding, 1)
	err := src.Sweep(context.Background(), "", out)
	if !errors.Is(err, ErrUnauthorized) {
		t.Fatalf("err = %v, want ErrUnauthorized", err)
	}
}

func TestGist_ContextCancellation(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(2 * time.Second)
		_, _ = w.Write([]byte(`[]`))
	}))
	t.Cleanup(srv.Close)
	src := newGistSource(srv.URL, "tok")
	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 on cancelled ctx")
	}
}
