package engine

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
)

// testReporter is a no-op ProgressReporter for testing
type testReporter struct{}

func (tr *testReporter) OnProgress(update ProgressUpdate) {}
func (tr *testReporter) OnError(err ErrorInfo)            {}
func (tr *testReporter) OnFinding(finding Finding)        {}

// setupTestWorkerPool creates a workerPool for testing with the given httptest client
func setupTestWorkerPool(client *http.Client) *workerPool {
	cfg := DefaultConfig()
	stats := &Stats{}
	rateLimit := NewRateLimitState()
	reporter := &testReporter{}
	
	return newWorkerPool(cfg, client, reporter, stats, rateLimit)
}

func TestCheckPath_ContentTypeValidation(t *testing.T) {
	tests := []struct {
		name        string
		path        string
		contentType string
		body        string
		wantExposed bool
	}{
		{
			name:        "reject text/html for .env",
			path:        "/.env",
			contentType: "text/html",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: false,
		},
		{
			name:        "reject text/html with charset",
			path:        "/.env",
			contentType: "text/html; charset=utf-8",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: false,
		},
		{
			name:        "reject TEXT/HTML case insensitive",
			path:        "/.env",
			contentType: "TEXT/HTML",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: false,
		},
		{
			name:        "accept text/plain with KEY=VALUE",
			path:        "/.env",
			contentType: "text/plain",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: true,
		},
		{
			name:        "accept application/octet-stream with KEY=VALUE",
			path:        "/.env",
			contentType: "application/octet-stream",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: true,
		},
		{
			name:        "accept empty content-type with KEY=VALUE",
			path:        "/.env",
			contentType: "",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// Return different content for homepage vs path to avoid soft 404
				if r.URL.Path == "/" {
					w.Header().Set("Content-Type", "text/html")
					w.WriteHeader(http.StatusOK)
					w.Write([]byte("<html><body>Homepage</body></html>"))
					return
				}

				if tt.contentType != "" {
					w.Header().Set("Content-Type", tt.contentType)
				}
				w.WriteHeader(http.StatusOK)
				w.Write([]byte(tt.body))
			}))
			defer ts.Close()

			wp := setupTestWorkerPool(ts.Client())

			// Extract domain from test server URL (remove http://)
			domain := ts.URL[7:] // Skip "http://"

			finding, err := wp.checkPath(context.Background(), domain, tt.path)
			if err != nil {
				t.Fatalf("checkPath() error = %v", err)
			}

			if finding == nil {
				t.Fatal("checkPath() returned nil finding")
			}

			if finding.Exposed != tt.wantExposed {
				t.Errorf("checkPath() Exposed = %v, want %v", finding.Exposed, tt.wantExposed)
			}
		})
	}
}

func TestCheckPath_HTMLRejection(t *testing.T) {
	tests := []struct {
		name        string
		body        string
		wantExposed bool
	}{
		{
			name:        "reject body with <!doctype html>",
			body:        "<!doctype html><html><body>Not Found</body></html>",
			wantExposed: false,
		},
		{
			name:        "reject body with <HTML>",
			body:        "<HTML><HEAD><TITLE>Error</TITLE></HEAD></HTML>",
			wantExposed: false,
		},
		{
			name:        "reject body with <head>",
			body:        "<head><title>404 Not Found</title></head>",
			wantExposed: false,
		},
		{
			name:        "accept text/plain with valid KEY=VALUE, no HTML",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// Return different content for homepage vs path to avoid soft 404
				if r.URL.Path == "/" {
					w.Header().Set("Content-Type", "text/html")
					w.WriteHeader(http.StatusOK)
					w.Write([]byte("<html><body>Homepage</body></html>"))
					return
				}

				w.Header().Set("Content-Type", "text/plain")
				w.WriteHeader(http.StatusOK)
				w.Write([]byte(tt.body))
			}))
			defer ts.Close()

			wp := setupTestWorkerPool(ts.Client())
			domain := ts.URL[7:]

			finding, err := wp.checkPath(context.Background(), domain, "/.env")
			if err != nil {
				t.Fatalf("checkPath() error = %v", err)
			}

			if finding == nil {
				t.Fatal("checkPath() returned nil finding")
			}

			if finding.Exposed != tt.wantExposed {
				t.Errorf("checkPath() Exposed = %v, want %v (body: %s)", finding.Exposed, tt.wantExposed, tt.body[:50])
			}
		})
	}
}

func TestCheckPath_ContextAwarePatterns(t *testing.T) {
	tests := []struct {
		name        string
		body        string
		wantExposed bool
		wantPattern string
	}{
		{
			name:        "detect .env with 3+ KEY=VALUE lines",
			body:        "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed: true,
			wantPattern: ".env-exposed",
		},
		{
			name:        "reject prose with generic word",
			body:        "Enter your password to continue",
			wantExposed: false,
		},
		{
			name:        "detect git config with [core] section",
			body:        "[core]\n\trepositoryformatversion = 0\n\tfilemode = true",
			wantExposed: true,
			wantPattern: ".git-exposed",
		},
		{
			name:        "reject only 1 generic KEY=VALUE line",
			body:        "SOME_CONFIG=value",
			wantExposed: false,
		},
		{
			name:        "detect specific key DB_PASSWORD=",
			body:        "DB_PASSWORD=verysecret123",
			wantExposed: true,
			wantPattern: "DB_PASSWORD",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// Return different content for homepage vs path to avoid soft 404
				if r.URL.Path == "/" {
					w.Header().Set("Content-Type", "text/html")
					w.WriteHeader(http.StatusOK)
					w.Write([]byte("<html><body>Homepage</body></html>"))
					return
				}

				w.Header().Set("Content-Type", "text/plain")
				w.WriteHeader(http.StatusOK)
				w.Write([]byte(tt.body))
			}))
			defer ts.Close()

			wp := setupTestWorkerPool(ts.Client())
			domain := ts.URL[7:]

			finding, err := wp.checkPath(context.Background(), domain, "/.env")
			if err != nil {
				t.Fatalf("checkPath() error = %v", err)
			}

			if finding == nil {
				t.Fatal("checkPath() returned nil finding")
			}

			if finding.Exposed != tt.wantExposed {
				t.Errorf("checkPath() Exposed = %v, want %v", finding.Exposed, tt.wantExposed)
			}

			if tt.wantExposed && tt.wantPattern != "" {
				found := false
				for _, pattern := range finding.Patterns {
					if pattern == tt.wantPattern {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("checkPath() patterns = %v, want to include %q", finding.Patterns, tt.wantPattern)
				}
			}
		})
	}
}

func TestCheckPath_Soft404Detection(t *testing.T) {
	tests := []struct {
		name         string
		homepageBody string
		pathBody     string
		wantExposed  bool
		description  string
	}{
		{
			name:         "same content at homepage and path - soft 404",
			homepageBody: "DB_HOST=localhost\nDB_USER=admin\nDB_PASSWORD=secret",
			pathBody:     "DB_HOST=localhost\nDB_USER=admin\nDB_PASSWORD=secret",
			wantExposed:  false,
			description:  "Identical content should be rejected as soft 404",
		},
		{
			name:         "different content - real finding",
			homepageBody: "<html><body>Welcome</body></html>",
			pathBody:     "DB_PASSWORD=secret\nAPI_KEY=test123\nREDIS_URL=redis://localhost",
			wantExposed:  true,
			description:  "Different content should be exposed",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.Header().Set("Content-Type", "text/plain")
				w.WriteHeader(http.StatusOK)
				
				if r.URL.Path == "/" {
					w.Write([]byte(tt.homepageBody))
				} else {
					w.Write([]byte(tt.pathBody))
				}
			}))
			defer ts.Close()

			wp := setupTestWorkerPool(ts.Client())
			domain := ts.URL[7:]
			
			// First, populate baseline by fetching homepage
			baseline := wp.getBaseline(context.Background(), domain)
			if baseline.contentLength != len(tt.homepageBody) {
				t.Fatalf("baseline.contentLength = %d, want %d", baseline.contentLength, len(tt.homepageBody))
			}
			
			// Now check the path
			finding, err := wp.checkPath(context.Background(), domain, "/.env")
			if err != nil {
				t.Fatalf("checkPath() error = %v", err)
			}

			if finding == nil {
				t.Fatal("checkPath() returned nil finding")
			}

			if finding.Exposed != tt.wantExposed {
				t.Errorf("checkPath() Exposed = %v, want %v - %s", finding.Exposed, tt.wantExposed, tt.description)
			}
		})
	}
}

func TestCheckPath_NonHTTP200(t *testing.T) {
	tests := []struct {
		name       string
		statusCode int
		wantExposed bool
	}{
		{
			name:       "404 response - not exposed",
			statusCode: 404,
			wantExposed: false,
		},
		{
			name:       "301 redirect - not exposed",
			statusCode: 301,
			wantExposed: false,
		},
		{
			name:       "500 error - not exposed",
			statusCode: 500,
			wantExposed: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tt.statusCode)
				w.Write([]byte("Error"))
			}))
			defer ts.Close()

			wp := setupTestWorkerPool(ts.Client())
			domain := ts.URL[7:]
			
			finding, err := wp.checkPath(context.Background(), domain, "/.env")
			if err != nil {
				t.Fatalf("checkPath() error = %v", err)
			}

			if finding == nil {
				t.Fatal("checkPath() returned nil finding")
			}

			if finding.Exposed != tt.wantExposed {
				t.Errorf("checkPath() Exposed = %v, want %v", finding.Exposed, tt.wantExposed)
			}
		})
	}
}
