package sources

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"golang.org/x/time/rate"

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

func newCodebergTestRegistry() *providers.Registry {
	return providers.NewRegistryFromProviders([]providers.Provider{
		{
			Name:          "openai",
			DisplayName:   "OpenAI",
			Tier:          1,
			Keywords:      []string{"sk-proj-"},
			FormatVersion: 1,
		},
	})
}

func TestCodebergSource_NameAndInterface(t *testing.T) {
	var _ recon.ReconSource = (*CodebergSource)(nil)

	s := &CodebergSource{}
	if got := s.Name(); got != "codeberg" {
		t.Errorf("Name() = %q, want %q", got, "codeberg")
	}
	if s.RespectsRobots() {
		t.Errorf("RespectsRobots() = true, want false")
	}
	if !s.Enabled(recon.Config{}) {
		t.Errorf("Enabled() = false, want true (public API)")
	}
}

func TestCodebergSource_RateLimitUnauthenticated(t *testing.T) {
	s := &CodebergSource{}
	got := s.RateLimit()
	want := rate.Every(60 * time.Second)
	if got != want {
		t.Errorf("RateLimit() no token = %v, want %v", got, want)
	}
	if s.Burst() != 1 {
		t.Errorf("Burst() = %d, want 1", s.Burst())
	}
}

func TestCodebergSource_RateLimitAuthenticated(t *testing.T) {
	s := &CodebergSource{Token: "abc123"}
	got := s.RateLimit()
	want := rate.Every(3600 * time.Millisecond)
	if got != want {
		t.Errorf("RateLimit() with token = %v, want %v", got, want)
	}
}

func TestCodebergSource_SweepEmitsFindings(t *testing.T) {
	var gotAuth string
	var 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("q")
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{
			"ok": true,
			"data": []map[string]any{
				{
					"full_name": "alice/leaked-keys",
					"html_url":  "https://codeberg.org/alice/leaked-keys",
				},
			},
		})
	}))
	defer srv.Close()

	s := &CodebergSource{
		BaseURL:  srv.URL,
		Registry: newCodebergTestRegistry(),
		Limiters: recon.NewLimiterRegistry(),
	}

	out := make(chan recon.Finding, 8)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := s.Sweep(ctx, "", out); err != nil {
		t.Fatalf("Sweep: %v", err)
	}
	close(out)

	var findings []recon.Finding
	for f := range out {
		findings = append(findings, f)
	}
	if len(findings) == 0 {
		t.Fatalf("expected at least one finding")
	}

	f := findings[0]
	if f.Source != "https://codeberg.org/alice/leaked-keys" {
		t.Errorf("Source = %q, want codeberg html_url", f.Source)
	}
	if f.SourceType != "recon:codeberg" {
		t.Errorf("SourceType = %q, want recon:codeberg", f.SourceType)
	}
	if f.ProviderName != "openai" {
		t.Errorf("ProviderName = %q, want openai", f.ProviderName)
	}

	if gotPath != "/api/v1/repos/search" {
		t.Errorf("path = %q, want /api/v1/repos/search", gotPath)
	}
	if gotQuery == "" {
		t.Errorf("query param empty")
	}
	if gotAuth != "" {
		t.Errorf("Authorization header should be absent without token, got %q", gotAuth)
	}
}

func TestCodebergSource_SweepWithTokenSetsAuthHeader(t *testing.T) {
	var gotAuth string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotAuth = r.Header.Get("Authorization")
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"ok":true,"data":[]}`))
	}))
	defer srv.Close()

	s := &CodebergSource{
		Token:    "s3cret",
		BaseURL:  srv.URL,
		Registry: newCodebergTestRegistry(),
		Limiters: recon.NewLimiterRegistry(),
	}
	out := make(chan recon.Finding, 1)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := s.Sweep(ctx, "", out); err != nil {
		t.Fatalf("Sweep: %v", err)
	}

	if !strings.HasPrefix(gotAuth, "token ") || !strings.Contains(gotAuth, "s3cret") {
		t.Errorf("Authorization header = %q, want \"token s3cret\"", gotAuth)
	}
}

func TestCodebergSource_SweepContextCancellation(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		select {
		case <-r.Context().Done():
		case <-time.After(3 * time.Second):
		}
	}))
	defer srv.Close()

	s := &CodebergSource{
		BaseURL:  srv.URL,
		Registry: newCodebergTestRegistry(),
		Limiters: recon.NewLimiterRegistry(),
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	out := make(chan recon.Finding, 1)
	err := s.Sweep(ctx, "", out)
	if err == nil {
		t.Fatalf("expected error on cancelled context")
	}
}
