package engine

import (
	"testing"
	"time"
)

func TestIsValidContentType(t *testing.T) {
	tests := []struct {
		name        string
		path        string
		contentType string
		want        bool
	}{
		// HTML rejection cases
		{
			name:        "reject text/html for .env",
			path:        "/.env",
			contentType: "text/html",
			want:        false,
		},
		{
			name:        "reject text/html with charset parameter",
			path:        "/.env",
			contentType: "text/html; charset=utf-8",
			want:        false,
		},
		{
			name:        "reject text/html case-insensitive",
			path:        "/.env",
			contentType: "TEXT/HTML",
			want:        false,
		},
		{
			name:        "reject text/html with multiple parameters",
			path:        "/.env",
			contentType: "text/html; charset=utf-8; boundary=something",
			want:        false,
		},

		// Valid content types
		{
			name:        "accept text/plain",
			path:        "/.env",
			contentType: "text/plain",
			want:        true,
		},
		{
			name:        "accept application/octet-stream",
			path:        "/.env",
			contentType: "application/octet-stream",
			want:        true,
		},
		{
			name:        "accept application/json",
			path:        "/swagger.json",
			contentType: "application/json",
			want:        true,
		},
		{
			name:        "accept empty content-type",
			path:        "/.env",
			contentType: "",
			want:        true,
		},
		{
			name:        "accept application/zip",
			path:        "/backup.zip",
			contentType: "application/zip",
			want:        true,
		},
		{
			name:        "accept application/sql",
			path:        "/dump.sql",
			contentType: "application/sql",
			want:        true,
		},
		{
			name:        "accept application/xml",
			path:        "/config.xml",
			contentType: "application/xml",
			want:        true,
		},

		// Reject invalid content types
		{
			name:        "reject image/png",
			path:        "/.env",
			contentType: "image/png",
			want:        false,
		},
		{
			name:        "reject video/mp4",
			path:        "/.env",
			contentType: "video/mp4",
			want:        false,
		},
		{
			name:        "reject audio/mpeg",
			path:        "/.env",
			contentType: "audio/mpeg",
			want:        false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isValidContentType(tt.path, tt.contentType)
			if got != tt.want {
				t.Errorf("isValidContentType(%q, %q) = %v, want %v", tt.path, tt.contentType, got, tt.want)
			}
		})
	}
}

func TestContainsHTMLIndicators(t *testing.T) {
	tests := []struct {
		name string
		body string
		want bool
	}{
		{
			name: "detect <!doctype html>",
			body: "<!doctype html><html><head><title>Error</title></head></html>",
			want: true,
		},
		{
			name: "detect <HTML> uppercase",
			body: "<HTML><HEAD><TITLE>Not Found</TITLE></HEAD></HTML>",
			want: true,
		},
		{
			name: "detect simple html body",
			body: "<html><body>Not Found</body></html>",
			want: true,
		},
		{
			name: "detect <meta> tag",
			body: "<meta name='viewport' content='width=device-width'>",
			want: true,
		},
		{
			name: "detect <script> tag",
			body: "<script>alert('test');</script>",
			want: true,
		},
		{
			name: "detect <head> tag",
			body: "<head><title>Error 404</title></head>",
			want: true,
		},
		{
			name: "detect <body> tag",
			body: "Some text before <body class='error'>Content</body>",
			want: true,
		},
		{
			name: "no HTML in env file",
			body: "DB_PASSWORD=secret\nAPI_KEY=abc123\nREDIS_URL=redis://localhost",
			want: false,
		},
		{
			name: "no HTML in git config",
			body: "[core]\n\trepositoryformatversion = 0\n[remote \"origin\"]\n\turl = git@github.com:user/repo.git",
			want: false,
		},
		{
			name: "empty body",
			body: "",
			want: false,
		},
		{
			name: "plain text without HTML tags",
			body: "This is just plain text with no markup",
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := containsHTMLIndicators(tt.body)
			if got != tt.want {
				t.Errorf("containsHTMLIndicators() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestMatchPatternsContextAware(t *testing.T) {
	tests := []struct {
		name string
		body string
		want []string
	}{
		{
			name: "detect .env with 3+ KEY=VALUE lines",
			body: "DB_PASSWORD=secret\nAPI_KEY=abc123\nREDIS_URL=redis://localhost",
			want: []string{".env-exposed"},
		},
		{
			name: "detect .env with many lines",
			body: "DB_HOST=localhost\nDB_USER=admin\nDB_PASSWORD=secret\nAPI_KEY=abc123\nREDIS_URL=redis://localhost\nSMTP_PASSWORD=mail123",
			want: []string{".env-exposed"},
		},
		{
			name: "reject .env with only 1 KEY=VALUE line",
			body: "DB_PASSWORD=secret",
			want: []string{},
		},
		{
			name: "reject .env with only 2 KEY=VALUE lines",
			body: "DB_PASSWORD=secret\nAPI_KEY=abc123",
			want: []string{},
		},
		{
			name: "reject HTML input fields",
			body: "<input type='password' name='pwd'>",
			want: []string{},
		},
		{
			name: "detect git config with [core] section",
			body: "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n[remote \"origin\"]\n\turl = git@github.com:user/repo.git",
			want: []string{".git-exposed"},
		},
		{
			name: "detect git config with [remote] section",
			body: "[remote \"origin\"]\n\turl = https://github.com/user/repo.git\n\tfetch = +refs/heads/*:refs/remotes/origin/*",
			want: []string{".git-exposed"},
		},
		{
			name: "detect git config with [branch] section",
			body: "[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main",
			want: []string{".git-exposed"},
		},
		{
			name: "detect git config with [user] section",
			body: "[user]\n\tname = John Doe\n\temail = john@example.com",
			want: []string{".git-exposed"},
		},
		{
			name: "reject random INI file without git sections",
			body: "[section]\nrandom=stuff\nother=value",
			want: []string{},
		},
		{
			name: "reject generic word in prose",
			body: "Enter your password to continue with the authentication process.",
			want: []string{},
		},
		{
			name: "reject URL parameters",
			body: "token=abc&session=xyz&user=admin",
			want: []string{},
		},
		{
			name: "empty body",
			body: "",
			want: []string{},
		},
		{
			name: "detect both .env and git config",
			body: "[core]\n\trepositoryformatversion = 0\nDB_PASSWORD=secret\nAPI_KEY=abc123\nREDIS_URL=redis://localhost",
			want: []string{".env-exposed", ".git-exposed"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := matchPatternsContextAware(tt.body)

			// Check length first
			if len(got) != len(tt.want) {
				t.Errorf("matchPatternsContextAware() returned %d matches, want %d. Got: %v, Want: %v",
					len(got), len(tt.want), got, tt.want)
				return
			}

			// Check each expected pattern is present
			for _, wantPattern := range tt.want {
				found := false
				for _, gotPattern := range got {
					if gotPattern == wantPattern {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("matchPatternsContextAware() missing expected pattern %q. Got: %v", wantPattern, got)
				}
			}
		})
	}
}

func TestIsSoft404(t *testing.T) {
	tests := []struct {
		name        string
		bodyLen     int
		baseline    baseline
		expectSoft4 bool
	}{
		{
			name:    "identical content length",
			bodyLen: 1000,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "content within 5% threshold - slightly smaller",
			bodyLen: 980,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "content within 5% threshold - slightly larger",
			bodyLen: 1020,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "content exactly at 5% boundary - lower",
			bodyLen: 950,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "content exactly at 5% boundary - upper",
			bodyLen: 1050,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "content beyond 5% threshold - smaller",
			bodyLen: 940,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: false,
		},
		{
			name:    "content beyond 5% threshold - larger",
			bodyLen: 1060,
			baseline: baseline{
				contentLength: 1000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: false,
		},
		{
			name:    "no baseline available - fail open",
			bodyLen: 1000,
			baseline: baseline{
				contentLength: 0,
				fetchedAt:     time.Time{},
			},
			expectSoft4: false,
		},
		{
			name:    "very small baseline - within threshold",
			bodyLen: 10,
			baseline: baseline{
				contentLength: 10,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "very small baseline - beyond threshold",
			bodyLen: 20,
			baseline: baseline{
				contentLength: 10,
				fetchedAt:     time.Now(),
			},
			expectSoft4: false,
		},
		{
			name:    "large baseline - threshold works at scale",
			bodyLen: 105000,
			baseline: baseline{
				contentLength: 100000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: true,
		},
		{
			name:    "large baseline - beyond threshold at scale",
			bodyLen: 106000,
			baseline: baseline{
				contentLength: 100000,
				fetchedAt:     time.Now(),
			},
			expectSoft4: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isSoft404(tt.bodyLen, tt.baseline)
			if got != tt.expectSoft4 {
				t.Errorf("isSoft404(%d, baseline{contentLength: %d}) = %v, want %v",
					tt.bodyLen, tt.baseline.contentLength, got, tt.expectSoft4)
			}
		})
	}
}
