package main

import (
	"bufio"
	"context"
	"crypto/tls"
	"encoding/csv"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gdamore/tcell/v2"
)

// ===========================================================================
// HUNTR MODULE (Module 5) — Credential Exposure Scanner
// ===========================================================================
//
// INSERTION INSTRUCTIONS:
//
// 1. Paste all code below into main.go (after the Pipeline module code)
//
// 2. In executeCommand(), add this case inside the switch:
//        case ModuleHuntr:
//            tui.executeHuntrCommand()
//
// 3. In handleFilePickerInput(), update the go func switch to:
//        switch tui.activeModule {
//        case ModuleHashHunter:
//            tui.runBulkScan(ctx, selected)
//        case ModuleHuntr:
//            tui.runHuntrBulkFromFile(ctx, selected)
//        default:
//            tui.runBulkCheck(ctx, selected)
//        }
//
// ===========================================================================

// ---------------------------------------------------------------------------
// DATA: Credential paths (1,258 paths — full Huntr corpus)
// ---------------------------------------------------------------------------

var huntrCredentialPaths = []string{
	// ENVIRONMENT FILES
	"/.env",
	"/.env.bak",
	"/.env.old",
	"/.env.local",
	"/.env.production",
	"/.env.staging",
	"/.env.dev",
	"/.env.development",
	"/.env.example",
	"/.env.backup",
	"/.env.test",
	"/.env.testing",
	"/.env.prod",
	"/.env.live",
	"/.env.save",
	"/.env.orig",
	"/.env.dist",
	"/.env.sample",
	"/.env.docker",
	"/.env.swp",
	"/.env~",
	"/.env.production.local",
	"/.env.development.local",
	"/.env.test.local",
	"/env",
	"/env.js",
	"/env.json",
	"/env.yaml",
	"/env.yml",
	"/env.toml",
	"/env.ini",
	"/env.cfg",
	"/env.conf",
	"/.flaskenv",

	// GIT EXPOSURE
	"/.git/config",
	"/.git/HEAD",
	"/.git/index",
	"/.git/logs/HEAD",
	"/.git/logs/refs/heads/main",
	"/.git/logs/refs/heads/master",
	"/.git/refs/heads/main",
	"/.git/refs/heads/master",
	"/.git/COMMIT_EDITMSG",
	"/.git/description",
	"/.git/info/exclude",
	"/.git/packed-refs",
	"/.git/objects/info/packs",
	"/.gitconfig",
	"/.gitignore",
	"/.gitmodules",
	"/.gitattributes",

	// SVN / Mercurial
	"/.svn/entries",
	"/.svn/wc.db",
	"/.hg/hgrc",
	"/.hg/store/data",
	"/.bzr/README",

	// NEXT.JS / REACT / VERCEL
	"/next.config.js",
	"/next.config.mjs",
	"/next.config.ts",
	"/.next/build-manifest.json",
	"/.next/routes-manifest.json",
	"/.next/prerender-manifest.json",
	"/.next/server/pages-manifest.json",
	"/.next/server/middleware-manifest.json",
	"/.next/static/chunks/main.js",
	"/_next/data/",
	"/_next/static/chunks/app/layout.js",
	"/_next/static/chunks/webpack.js",
	"/.next/server/app/api/",
	"/.next/BUILD_ID",
	"/.next/package.json",
	"/vercel.json",
	"/.vercel/project.json",
	"/.vercel/README.txt",
	"/next-env.d.ts",

	// React / CRA / Vite
	"/static/js/main.js",
	"/static/js/bundle.js",
	"/static/js/main.chunk.js",
	"/static/js/0.chunk.js",
	"/static/js/vendors.js",
	"/build/static/js/main.js",
	"/dist/assets/index.js",
	"/assets/index.js",

	// Source maps
	"/main.js.map",
	"/bundle.js.map",
	"/app.js.map",
	"/static/js/main.js.map",
	"/static/js/bundle.js.map",
	"/static/js/main.chunk.js.map",
	"/build/static/js/main.js.map",
	"/dist/assets/index.js.map",
	"/assets/index.js.map",
	"/sourcemaps/",
	"/.map",

	// Vue.js
	"/vue.config.js",
	"/dist/js/app.js",
	"/dist/js/app.js.map",
	"/dist/js/chunk-vendors.js.map",

	// Angular
	"/angular.json",
	"/environment.ts",
	"/environment.prod.ts",
	"/assets/config.json",
	"/ngsw.json",

	// Nuxt.js
	"/nuxt.config.js",
	"/nuxt.config.ts",
	"/.nuxt/",
	"/_nuxt/",

	// SvelteKit
	"/svelte.config.js",
	"/.svelte-kit/",

	// Remix
	"/remix.config.js",

	// Astro
	"/astro.config.mjs",

	// FIREBASE / SUPABASE / APPWRITE
	"/firebase.json",
	"/.firebaserc",
	"/firebaseConfig.js",
	"/firebaseConfig.json",
	"/firebase-config.js",
	"/firebase-config.json",
	"/firebase-adminsdk.json",
	"/service-account.json",
	"/serviceAccountKey.json",
	"/google-services.json",
	"/firebase-debug.log",
	"/firestore.rules",
	"/storage.rules",
	"/database.rules.json",
	"/supabase/config.toml",
	"/.supabase/",
	"/supabase/.env",
	"/lib/supabase.ts",
	"/lib/supabase.js",
	"/src/lib/supabase.ts",
	"/src/lib/supabase.js",
	"/utils/supabase.ts",
	"/src/config/firebase.js",
	"/src/config/firebase.ts",
	"/appwrite.json",

	// AUTH PROVIDERS
	"/.clerk/",
	"/auth.config.js",
	"/auth.config.ts",
	"/auth.ts",
	"/auth.js",
	"/middleware.ts",
	"/middleware.js",
	"/auth0-config.json",
	"/.auth0",

	// PAYMENT / STRIPE / PAYPAL
	"/stripe-config.json",
	"/stripe-webhook-secret",
	"/payment-config.json",
	"/paypal.json",

	// DATABASE CONFIGS & ORM
	"/prisma/schema.prisma",
	"/prisma/.env",
	"/drizzle.config.ts",
	"/drizzle.config.js",
	"/knexfile.js",
	"/knexfile.ts",
	"/ormconfig.json",
	"/ormconfig.js",
	"/ormconfig.ts",
	"/typeorm.config.ts",
	"/sequelize.config.js",
	"/config/database.json",
	"/config/database.yml",
	"/config/database.php",
	"/config/db.js",
	"/config/db.json",
	"/database.json",
	"/database.yml",
	"/database.yaml",
	"/db.json",
	"/db.yaml",
	"/db.yml",
	"/config/config.json",
	"/config/default.json",
	"/config/production.json",
	"/config/development.json",
	"/mongoid.yml",
	"/mongorc.js",

	// DATABASE DUMPS & BACKUPS
	"/dump.sql",
	"/database.sql",
	"/db.sql",
	"/backup.sql",
	"/data.sql",
	"/dump.sql.gz",
	"/dump.sql.bz2",
	"/dump.sql.zip",
	"/backup.zip",
	"/backup.tar.gz",
	"/backup.tar.bz2",
	"/backup.rar",
	"/backup.7z",
	"/site.sql",
	"/mysql.sql",
	"/postgres.sql",
	"/pg_dump.sql",
	"/export.sql",
	"/db_backup.sql",
	"/database_backup.sql",
	"/db.sqlite",
	"/db.sqlite3",
	"/database.sqlite",
	"/database.sqlite3",
	"/data.db",
	"/app.db",
	"/production.sqlite3",
	"/development.sqlite3",
	"/users.sql",
	"/users.csv",
	"/customers.csv",
	"/clients.csv",
	"/accounts.csv",
	"/members.sql",

	// WORDPRESS
	"/wp-config.php",
	"/wp-config.php.bak",
	"/wp-config.php.old",
	"/wp-config.php~",
	"/wp-config.php.save",
	"/wp-config.php.swp",
	"/wp-config.php.orig",
	"/wp-config.php.txt",
	"/wp-config.txt",
	"/wp-config-sample.php",
	"/wp-content/debug.log",
	"/wp-content/uploads/",
	"/wp-includes/version.php",
	"/wp-login.php",
	"/wp-admin/install.php",
	"/wp-json/wp/v2/users",
	"/xmlrpc.php",
	"/wp-cron.php",
	"/readme.html",

	// PHP / LARAVEL / SYMFONY / CODEIGNITER / MAGENTO
	"/config.php",
	"/configuration.php",
	"/config.php.bak",
	"/config.php.old",
	"/config.php.save",
	"/config.php~",
	"/config.inc.php",
	"/config.inc",
	"/conf.php",
	"/db.php",
	"/database.php",
	"/connect.php",
	"/connection.php",
	"/conn.php",
	"/settings.php",
	"/setup.php",
	"/install.php",
	"/install/",
	"/installer/",
	"/config/app.php",
	"/config/database.php",
	"/config/mail.php",
	"/config/services.php",
	"/config/auth.php",
	"/config/filesystems.php",
	"/storage/logs/laravel.log",
	"/storage/framework/sessions/",
	"/storage/framework/cache/",
	"/bootstrap/cache/config.php",
	"/app/etc/local.xml",
	"/app/etc/env.php",
	"/parameters.yml",
	"/parameters.ini",
	"/var/log/system.log",
	"/var/log/debug.log",
	"/var/log/exception.log",
	"/vendor/composer/installed.json",

	// PYTHON / DJANGO / FLASK / FASTAPI
	"/settings.py",
	"/local_settings.py",
	"/config.py",
	"/config.cfg",
	"/config.ini",
	"/config.yaml",
	"/config.yml",
	"/config.toml",
	"/app/config.py",
	"/instance/config.py",
	"/instance/application.cfg",
	"/requirements.txt",
	"/Pipfile",
	"/Pipfile.lock",
	"/pyproject.toml",
	"/poetry.lock",
	"/.python-version",
	"/manage.py",
	"/wsgi.py",
	"/asgi.py",
	"/celeryconfig.py",
	"/alembic.ini",
	"/alembic/env.py",
	"/pytest.ini",
	"/.pytest_cache/",
	"/django_secret_key",

	// RUBY / RAILS
	"/config/secrets.yml",
	"/config/master.key",
	"/config/credentials.yml.enc",
	"/config/database.yml",
	"/config/storage.yml",
	"/config/cable.yml",
	"/config/environments/production.rb",
	"/config/initializers/devise.rb",
	"/config/initializers/secret_token.rb",
	"/Gemfile",
	"/Gemfile.lock",
	"/db/seeds.rb",
	"/db/schema.rb",
	"/tmp/cache/",
	"/log/production.log",
	"/log/development.log",

	// JAVA / SPRING / TOMCAT / JBOSS
	"/application.properties",
	"/application.yml",
	"/application.yaml",
	"/application-prod.properties",
	"/application-prod.yml",
	"/application-dev.properties",
	"/application-dev.yml",
	"/application-local.properties",
	"/bootstrap.properties",
	"/bootstrap.yml",
	"/WEB-INF/web.xml",
	"/WEB-INF/classes/application.properties",
	"/WEB-INF/classes/application.yml",
	"/META-INF/context.xml",
	"/META-INF/maven/",
	"/actuator",
	"/actuator/env",
	"/actuator/configprops",
	"/actuator/beans",
	"/actuator/health",
	"/actuator/info",
	"/actuator/mappings",
	"/actuator/heapdump",
	"/actuator/threaddump",
	"/actuator/logfile",
	"/jolokia/",
	"/console",
	"/h2-console",

	// .NET / ASP.NET / C#
	"/web.config",
	"/web.config.bak",
	"/web.config.old",
	"/web.config.txt",
	"/appsettings.json",
	"/appsettings.Development.json",
	"/appsettings.Production.json",
	"/appsettings.Staging.json",
	"/connectionstrings.config",
	"/machine.config",
	"/elmah.axd",
	"/trace.axd",
	"/global.asax",

	// GO
	"/go.mod",
	"/go.sum",
	"/config.go",
	"/.air.toml",
	"/cmd/",

	// RUST
	"/Cargo.toml",
	"/Cargo.lock",
	"/Rocket.toml",
	"/.cargo/config.toml",

	// NODE.JS / NPM / YARN / BUN / DENO
	"/package.json",
	"/package-lock.json",
	"/yarn.lock",
	"/pnpm-lock.yaml",
	"/bun.lockb",
	"/.npmrc",
	"/.yarnrc",
	"/.yarnrc.yml",
	"/node_modules/.package-lock.json",
	"/tsconfig.json",
	"/jsconfig.json",
	"/turbo.json",
	"/lerna.json",
	"/nx.json",
	"/deno.json",
	"/deno.jsonc",
	"/deno.lock",
	"/import_map.json",
	"/nest-cli.json",
	"/nodemon.json",
	"/.node-version",
	"/.nvmrc",
	"/ecosystem.config.js",
	"/pm2.config.js",
	"/wrangler.toml",

	// API DOCUMENTATION
	"/swagger.json",
	"/swagger.yaml",
	"/swagger.yml",
	"/swagger-ui/",
	"/swagger-ui.html",
	"/openapi.json",
	"/openapi.yaml",
	"/openapi.yml",
	"/api-docs",
	"/api-docs.json",
	"/api/docs",
	"/api/swagger",
	"/api/openapi",
	"/api/v1/docs",
	"/api/v2/docs",
	"/api/v3/docs",
	"/docs/api",
	"/redoc",
	"/graphql",
	"/graphiql",
	"/playground",
	"/altair",
	"/__graphql",
	"/graphql/console",
	"/graphql/schema",
	"/api/graphql",
	"/v1/graphql",
	"/api/config",
	"/api/v1/config",
	"/api/v2/config",
	"/api/settings",
	"/api/v1/settings",
	"/api/debug",
	"/api/test",
	"/api/health",
	"/api/status",
	"/api/info",
	"/api/version",
	"/api/env",
	"/api/admin",
	"/api/internal",
	"/api/private",
	"/api/keys",
	"/api/tokens",
	"/api/users",
	"/api/v1/users",
	"/api/v1/admin",
	"/api/me",

	// CLOUD PROVIDERS — AWS / GCP / AZURE / DO
	"/.aws/credentials",
	"/.aws/config",
	"/.aws/",
	"/aws.json",
	"/aws.yml",
	"/.boto",
	"/.s3cfg",
	"/gcloud/credentials.db",
	"/gcloud/application_default_credentials.json",
	"/google-cloud-sdk/properties",
	"/service-account-key.json",
	"/gcp-key.json",
	"/gcs-key.json",
	"/.azure/",
	"/azure.json",
	"/.digitalocean/config",

	// INFRASTRUCTURE AS CODE
	"/terraform.tfstate",
	"/terraform.tfstate.backup",
	"/terraform.tfvars",
	"/.terraform/",
	"/main.tf",
	"/variables.tf",
	"/outputs.tf",
	"/backend.tf",
	"/provider.tf",
	"/Pulumi.yaml",
	"/Pulumi.dev.yaml",
	"/Pulumi.prod.yaml",
	"/pulumi.json",
	"/ansible.cfg",
	"/playbook.yml",
	"/inventory",
	"/group_vars/all.yml",
	"/host_vars/",
	"/vault.yml",
	"/ansible-vault",
	"/Vagrantfile",
	"/vagrant.yml",

	// CONTAINERS & ORCHESTRATION
	"/Dockerfile",
	"/Dockerfile.prod",
	"/Dockerfile.dev",
	"/docker-compose.yml",
	"/docker-compose.yaml",
	"/docker-compose.prod.yml",
	"/docker-compose.dev.yml",
	"/docker-compose.override.yml",
	"/.docker/config.json",
	"/.dockerenv",
	"/kubernetes.yml",
	"/k8s/",
	"/.kube/config",
	"/kustomization.yml",
	"/kustomization.yaml",
	"/skaffold.yaml",
	"/helm/",
	"/Chart.yaml",
	"/values.yaml",
	"/values-prod.yaml",

	// CI/CD PIPELINES
	"/.travis.yml",
	"/.circleci/config.yml",
	"/.github/workflows",
	"/.github/workflows/ci.yml",
	"/.github/workflows/cd.yml",
	"/.github/workflows/deploy.yml",
	"/.github/workflows/build.yml",
	"/.github/workflows/test.yml",
	"/.github/workflows/release.yml",
	"/.gitlab-ci.yml",
	"/Jenkinsfile",
	"/bitbucket-pipelines.yml",
	"/.drone.yml",
	"/azure-pipelines.yml",
	"/buildspec.yml",
	"/cloudbuild.yaml",
	"/appveyor.yml",
	"/wercker.yml",
	"/.buildkite/pipeline.yml",
	"/netlify.toml",
	"/fly.toml",
	"/render.yaml",
	"/railway.json",
	"/railway.toml",
	"/Procfile",
	"/heroku.yml",
	"/app.yaml",
	"/app.yml",
	"/cdk.json",
	"/sam.yaml",
	"/serverless.yml",
	"/serverless.yaml",
	"/amplify.yml",

	// HOSTING / DEPLOYMENT CONFIGS
	"/vercel.json",
	"/firebase.json",
	"/now.json",
	"/rewrite.config.js",
	"/rewrites.json",
	"/redirects.json",
	"/render.yaml",
	"/Procfile",

	// SSH / CERTIFICATES / PRIVATE KEYS
	"/.ssh/id_rsa",
	"/.ssh/id_rsa.pub",
	"/.ssh/id_ed25519",
	"/.ssh/id_ed25519.pub",
	"/.ssh/id_dsa",
	"/.ssh/authorized_keys",
	"/.ssh/known_hosts",
	"/.ssh/config",
	"/id_rsa",
	"/id_rsa.pub",
	"/id_ed25519",
	"/server.key",
	"/server.pem",
	"/server.crt",
	"/private.key",
	"/private.pem",
	"/cert.pem",
	"/cert.key",
	"/fullchain.pem",
	"/privkey.pem",
	"/ssl/private.key",
	"/ssl/server.key",
	"/ssl/cert.pem",
	"/tls.key",
	"/tls.crt",
	"/.pem",
	"/jwt.key",
	"/jwt-key.pem",
	"/jwtRS256.key",
	"/jwtRS256.key.pub",
	"/signing.key",
	"/encryption.key",
	"/oauth-private.key",
	"/oauth-public.key",
	"/saml.pem",
	"/saml.key",

	// IDE / EDITOR FILES
	"/.vscode/settings.json",
	"/.vscode/launch.json",
	"/.vscode/tasks.json",
	"/.vscode/extensions.json",
	"/.vscode/.env",
	"/.idea/workspace.xml",
	"/.idea/dataSources.xml",
	"/.idea/dataSources.local.xml",
	"/.idea/webServers.xml",
	"/.idea/deployment.xml",
	"/.idea/modules.xml",
	"/.idea/vcs.xml",
	"/.idea/",
	"/.project",
	"/.classpath",
	"/.settings/",
	"/.sublime-project",
	"/.sublime-workspace",
	"/nbproject/project.properties",
	"/.editorconfig",

	// DEBUG / INFO / STATUS PAGES
	"/phpinfo.php",
	"/info.php",
	"/test.php",
	"/debug",
	"/debug/",
	"/debug.log",
	"/debug.txt",
	"/error.log",
	"/error_log",
	"/errors.log",
	"/access.log",
	"/access_log",
	"/app.log",
	"/application.log",
	"/server.log",
	"/output.log",
	"/console.log",
	"/npm-debug.log",
	"/yarn-debug.log",
	"/yarn-error.log",
	"/crash.log",
	"/server-status",
	"/server-info",
	"/_debug",
	"/__debug__/",
	"/debug/default/view",
	"/debug/pprof/",
	"/health",
	"/healthcheck",
	"/healthz",
	"/readyz",
	"/livez",
	"/status",
	"/ping",
	"/version",
	"/build-info",
	"/metrics",
	"/prometheus",
	"/_status",
	"/_health",
	"/_info",
	"/trace",
	"/trace.log",

	// ADMIN PANELS & MANAGEMENT
	"/admin",
	"/admin/",
	"/admin/login",
	"/admin/config",
	"/admin/dashboard",
	"/admin/settings",
	"/admin/users",
	"/administrator",
	"/administrator/",
	"/cpanel",
	"/cpanel/",
	"/phpmyadmin",
	"/phpmyadmin/",
	"/pma",
	"/pma/",
	"/adminer.php",
	"/adminer",
	"/myadmin",
	"/myadmin/",
	"/dbadmin",
	"/dbadmin/",
	"/sqladmin",
	"/sqladmin/",
	"/mysql",
	"/mysql/",
	"/pgadmin",
	"/pgadmin/",
	"/mongo-express",
	"/mongo-express/",
	"/redis-commander",
	"/kibana",
	"/grafana",
	"/portainer",
	"/traefik",
	"/consul",
	"/vault/ui",
	"/jenkins",
	"/jenkins/",
	"/hudson",
	"/bamboo",
	"/sonarqube",
	"/_admin",
	"/_admin/",
	"/manage",
	"/manage/",
	"/management",
	"/management/",
	"/panel",
	"/panel/",
	"/dashboard",
	"/dashboard/",
	"/control",
	"/controlpanel",
	"/sysadmin",
	"/webadmin",
	"/filemanager",
	"/wp-admin/",
	"/cms/admin",
	"/cms/login",
	"/strapi",
	"/strapi/admin",
	"/directus/admin",
	"/sanity/",

	// SECRETS / CREDENTIALS / TOKEN FILES
	"/credentials.json",
	"/credentials.yml",
	"/credentials.yaml",
	"/credentials.xml",
	"/secrets.json",
	"/secrets.yml",
	"/secrets.yaml",
	"/secrets.xml",
	"/secrets.txt",
	"/secret",
	"/secret.key",
	"/secret.txt",
	"/token.json",
	"/tokens.json",
	"/auth.json",
	"/auth.yml",
	"/keys.json",
	"/keys.yml",
	"/apikeys.json",
	"/api-keys.json",
	"/api_keys.json",
	"/api-key.txt",
	"/passwords.txt",
	"/passwords.csv",
	"/passwd",
	"/shadow",
	"/master.key",
	"/encryption.key",
	"/keyfile",
	"/keystore",
	"/keystore.jks",
	"/truststore.jks",
	"/.keystore",
	"/.truststore",
	"/vault-keys.json",
	"/.vault-token",
	"/.netrc",
	"/.wgetrc",
	"/.curlrc",
	"/.wget-hsts",

	// ACCESS CONTROL & SERVER CONFIG
	"/.htpasswd",
	"/.htaccess",
	"/.htaccess.bak",
	"/.htaccess.old",
	"/.htaccess~",
	"/.htpasswd.bak",
	"/nginx.conf",
	"/nginx/nginx.conf",
	"/etc/nginx/nginx.conf",
	"/conf/nginx.conf",
	"/apache2.conf",
	"/httpd.conf",
	"/lighttpd.conf",
	"/crossdomain.xml",
	"/clientaccesspolicy.xml",
	"/robots.txt",
	"/sitemap.xml",
	"/security.txt",
	"/.well-known/security.txt",
	"/.well-known/openid-configuration",
	"/.well-known/jwks.json",
	"/.well-known/assetlinks.json",
	"/.well-known/apple-app-site-association",

	// CMS SYSTEMS — Drupal / Joomla / Magento / Ghost
	"/sites/default/settings.php",
	"/sites/default/files/",
	"/core/install.php",
	"/CHANGELOG.txt",
	"/UPDATE.txt",
	"/INSTALL.txt",
	"/configuration.php",
	"/configuration.php.bak",
	"/joomla.xml",
	"/administrator/manifests/files/joomla.xml",
	"/app/etc/env.php",
	"/app/etc/local.xml",
	"/var/log/system.log",
	"/var/log/exception.log",
	"/downloader/",
	"/ghost/api/v3/admin/",
	"/ghost/api/content/",
	"/content/config.json",

	// MOBILE / REACT NATIVE / FLUTTER / EXPO
	"/app.json",
	"/expo-constants.json",
	"/google-services.json",
	"/GoogleService-Info.plist",
	"/Info.plist",
	"/config.xml",
	"/build.gradle",
	"/local.properties",
	"/gradle.properties",

	// MESSAGING / EMAIL / NOTIFICATION SERVICES
	"/mailgun.json",
	"/sendgrid.json",
	"/smtp-config.json",
	"/email-config.json",
	"/twilio.json",
	"/pusher.json",
	"/sentry.properties",
	"/.sentryclirc",

	// MONITORING / ANALYTICS / LOGGING
	"/newrelic.yml",
	"/newrelic.js",
	"/datadog.yaml",
	"/bugsnag.json",
	"/rollbar.json",
	"/loggly.json",
	"/splunk.conf",
	"/elastic.yml",
	"/logstash.conf",
	"/filebeat.yml",

	// PACKAGE MANAGER CONFIGS
	"/.npmrc",
	"/.yarnrc",
	"/.yarnrc.yml",
	"/.pip/pip.conf",
	"/.pypirc",
	"/pip.conf",
	"/.gemrc",
	"/.bundler/config",
	"/composer.json",
	"/composer.lock",
	"/cargo-config.toml",
	"/.m2/settings.xml",
	"/gradle/wrapper/gradle-wrapper.properties",
	"/nuget.config",
	"/.nuget/NuGet.Config",
	"/paket.dependencies",

	// BACKUP / ARCHIVE FILES
	"/backup/",
	"/backups/",
	"/bak/",
	"/old/",
	"/temp/",
	"/tmp/",
	"/archive/",
	"/archives/",
	"/index.php.bak",
	"/index.php~",
	"/index.php.old",
	"/index.html.bak",
	"/index.html~",
	"/app.bak",
	"/site.bak",
	"/main.bak",
	"/www.zip",
	"/www.tar.gz",
	"/web.zip",
	"/web.tar.gz",
	"/site.zip",
	"/site.tar.gz",
	"/html.zip",
	"/html.tar.gz",
	"/public.zip",
	"/src.zip",
	"/source.zip",
	"/code.zip",
	"/deploy.zip",
	"/release.zip",
	"/latest.zip",
	"/latest.tar.gz",

	// TEMP / SWAP / EDITOR BACKUP FILES
	"/.DS_Store",
	"/Thumbs.db",
	"/desktop.ini",
	"/.directory",
	"/.bash_history",
	"/.zsh_history",
	"/.mysql_history",
	"/.psql_history",
	"/.python_history",
	"/.node_repl_history",
	"/.irb_history",
	"/.lesshst",
	"/.viminfo",
	"/.swp",
	"/~",
	"/._",
	"/.bak",
	"/.tmp",
	"/.old",
	"/.orig",
	"/.save",
	"/.copy",

	// VIBE CODER HARDCODED SECRETS
	"/constants.js",
	"/constants.ts",
	"/src/constants.js",
	"/src/constants.ts",
	"/src/config.js",
	"/src/config.ts",
	"/src/config/index.js",
	"/src/config/index.ts",
	"/lib/config.js",
	"/lib/config.ts",
	"/utils/config.js",
	"/utils/config.ts",
	"/src/utils/api.js",
	"/src/utils/api.ts",
	"/src/services/api.js",
	"/src/services/api.ts",

	// DOCKER RUNTIME SECRETS
	"/run/secrets/",
	"/run/secrets/db_password",
	"/run/secrets/db_root_password",

	// MISCELLANEOUS / CATCH-ALL
	"/LICENSE",
	"/README.md",
	"/README.txt",
	"/CHANGELOG.md",
	"/CHANGELOG.txt",
	"/TODO.md",
	"/TODO.txt",
	"/NOTES.md",
	"/INSTALL.md",
	"/.dockerignore",
	"/.eslintrc",
	"/.eslintrc.json",
	"/.prettierrc",
	"/.babelrc",
	"/tailwind.config.js",
	"/tailwind.config.ts",
	"/postcss.config.js",
	"/webpack.config.js",
	"/webpack.config.ts",
	"/rollup.config.js",
	"/vite.config.js",
	"/vite.config.ts",
	"/esbuild.config.js",
	"/wrangler.toml",
	"/workers-site/",
	"/manifest.json",
	"/site.webmanifest",
	"/.well-known/",
	"/humans.txt",
	"/ads.txt",
	"/app-ads.txt",
	"/browserconfig.xml",

	// COMMON SENSITIVE DIRECTORIES
	"/private/",
	"/internal/",
	"/secret/",
	"/hidden/",
	"/confidential/",
	"/restricted/",
	"/uploads/",
	"/upload/",
	"/files/",
	"/documents/",
	"/docs/",
	"/data/",
	"/export/",
	"/exports/",
	"/import/",
	"/imports/",
	"/reports/",
	"/logs/",
	"/log/",
	"/debug/",
	"/test/",
	"/tests/",
	"/staging/",
	"/dev/",
	"/development/",
	"/sandbox/",
	"/demo/",
	"/sample/",
	"/examples/",
	"/migration/",
	"/migrations/",
	"/seeds/",
	"/fixtures/",
	"/sql/",
	"/scripts/",
	"/bin/",
	"/cgi-bin/",

	// AI CODING TOOLS — Replit / Cursor / Claude Code / Bolt / v0 / Lovable
	"/.replit",
	"/replit.nix",
	"/.replit.env",
	"/.replit/",
	"/replit.toml",
	"/.config/replit/",
	"/.cursorrules",
	"/.cursorignore",
	"/.cursorindexingignore",
	"/.cursor/",
	"/.cursor/settings.json",
	"/.cursor/mcp.json",
	"/CLAUDE.md",
	"/.claude/settings.json",
	"/.claude/",
	"/.claudeignore",
	"/.anthropic/config.json",
	"/claude_desktop_config.json",
	"/AGENTS.md",
	"/.agents/local.json",
	"/.bolt/config",
	"/.bolt/",
	"/.bolt/config.json",
	"/.bolt/ignore",
	"/.boltignore",
	"/bolt.yaml",
	"/bolt.service.yaml",
	"/.stackblitz/",
	"/.stackblitz.json",
	"/.lovable/",
	"/.lovable/config.json",
	"/.windsurf/",
	"/.windsurfrules",
	"/.codeium/",
	"/.copilot/",
	"/.copilot/config",
	"/.copilot/mcp-config.json",
	"/.github/copilot/",
	"/.github/copilot-setup-steps.yml",
	"/.base44/",
	"/.base44/config.json",
	"/base44.config.js",
	"/base44.config.json",
	"/.v0/",
	"/.v0/config.json",
	"/.envrc",
	"/.devin/",
	"/.devin/config.json",
	"/.aws/amazonq/mcp.json",
	"/.codex/",
	"/.codex/config.toml",
	"/.tempo/",
	"/.tempo/config.json",
	"/firebase.config.js",
	"/firebase.config.ts",
	"/mcp.json",
	"/.mcp/",
	"/.mcp/config.json",
	"/mcp-config.json",
	"/mcp-servers.json",
	"/.aider/",
	"/.aider.conf.yml",
	"/.continue/",
	"/.continue/config.json",
	"/.tabby/",
	"/.cody/",
	"/.tabnine/",
	"/.kiro/",
	"/.roo/",
	"/.roo/config.json",
	"/.zed/",
	"/.zed/settings.json",

	// VIBE CODER SPECIFIC
	"/.dev.vars",
	"/.env.vault",
	"/DOTENV_PRIVATE_KEY",
	"/.doppler/",
	"/.doppler/config.json",
	"/.infisical/",
	"/.infisical.json",
	"/doppler.yaml",
	"/infisical.json",
	"/convex/_generated/",
	"/convex/serviceAccountKey.json",
	"/convex.json",
	"/convex/convex.config.ts",
	"/main.wasp",
	"/.env.server",
	"/.env.client",
	"/.amplication/",
	"/retool.json",
	"/appsmith.json",
	"/budibase.json",
	"/tooljet.json",
	"/notebook.ipynb",
	"/main.ipynb",
	"/app.ipynb",
	"/index.ipynb",
	"/test.ipynb",
	"/demo.ipynb",
	"/config.ipynb",
	"/setup.ipynb",
	"/Untitled.ipynb",
	"/.ipynb_checkpoints/",
	"/.turso/",
	"/turso.json",
	"/.planetscale/",
	"/neon.json",
	"/.neon/",
	"/upstash.json",
	"/trpc-config.ts",
	"/hono.config.ts",
	"/prod.exs",
	"/dev.exs",
	"/config.exs",
	"/runtime.exs",
	"/prod.secret.exs",

	// SHELL / SYSTEM HISTORY
	"/.bash_history",
	"/.bashrc",
	"/.bash_profile",
	"/.profile",
	"/.zsh_history",
	"/.zshrc",
	"/.mysql_history",
	"/.psql_history",
	"/.python_history",
	"/.node_repl_history",
	"/.irb_history",
	"/.lesshst",
	"/.viminfo",
	"/.wget-hsts",

	// DATABASE CLIENT CONFIGS
	"/.pgpass",
	"/.my.cnf",
	"/mongodb.conf",
	"/redis.conf",
	"/redis.yaml",
	"/mongod.conf",
	"/pg_hba.conf",
	"/my.ini",
	"/my.cnf",

	// CRYPTOCURRENCY / WEB3
	"/hardhat.config.js",
	"/hardhat.config.ts",
	"/truffle-config.js",
	"/foundry.toml",
	"/.secret",
	"/mnemonic.txt",
	"/wallet.json",
	"/keystore.json",
	"/.openzeppelin/",
}

// ---------------------------------------------------------------------------
// DATA: Credential patterns (~404 patterns)
// ---------------------------------------------------------------------------

var huntrCredentialPatterns = []string{
	// Database credentials
	"DB_PASSWORD",
	"DB_USERNAME",
	"DB_HOST",
	"DB_DATABASE",
	"DB_PORT",
	"DB_CONNECTION",
	"DATABASE_URL",
	"DATABASE_HOST",
	"DATABASE_PASSWORD",
	"DATABASE_USERNAME",
	"MYSQL_PASSWORD",
	"MYSQL_ROOT_PASSWORD",
	"MYSQL_USER",
	"POSTGRES_PASSWORD",
	"POSTGRES_USER",
	"PGPASSWORD",
	"PGUSER",
	"MONGO_URI",
	"MONGODB_URI",
	"MONGO_URL",
	"REDIS_URL",
	"REDIS_PASSWORD",
	"REDIS_HOST",
	"SQLITE_PATH",
	"POSTGRES_URL",
	"POSTGRESQL_URL",
	"MYSQL_URL",
	"MYSQL_HOST",
	"MYSQL_DATABASE",
	"SUPABASE_URL",
	"SUPABASE_KEY",
	"SUPABASE_ANON_KEY",
	"SUPABASE_SERVICE_ROLE_KEY",
	"SUPABASE_JWT_SECRET",

	// AWS
	"AWS_ACCESS_KEY_ID",
	"AWS_SECRET_ACCESS_KEY",
	"AWS_SESSION_TOKEN",
	"AWS_DEFAULT_REGION",
	"AWS_ACCOUNT_ID",
	"AWS_LAMBDA_FUNCTION",
	"S3_BUCKET",
	"S3_KEY",
	"S3_SECRET",
	"S3_REGION",
	"S3_ENDPOINT",
	"CLOUDFRONT_URL",

	// GCP
	"GOOGLE_APPLICATION_CREDENTIALS",
	"GOOGLE_CLOUD_PROJECT",
	"GOOGLE_PRIVATE_KEY",
	"GOOGLE_CLIENT_EMAIL",
	"GOOGLE_CLIENT_ID",
	"GCP_PROJECT",
	"GCS_BUCKET",
	"FIREBASE_API_KEY",
	"FIREBASE_AUTH_DOMAIN",
	"FIREBASE_PROJECT_ID",
	"FIREBASE_STORAGE_BUCKET",
	"FIREBASE_MESSAGING_SENDER_ID",
	"FIREBASE_APP_ID",
	"FIREBASE_PRIVATE_KEY",
	"FIREBASE_CLIENT_EMAIL",
	"FIREBASE_TOKEN",
	"FIREBASE_DATABASE_URL",

	// Azure
	"AZURE_CLIENT_ID",
	"AZURE_CLIENT_SECRET",
	"AZURE_TENANT_ID",
	"AZURE_SUBSCRIPTION_ID",
	"AZURE_STORAGE_KEY",
	"AZURE_STORAGE_CONNECTION_STRING",

	// Generic API keys
	"API_KEY",
	"API_SECRET",
	"API_TOKEN",
	"APIKEY",
	"APP_KEY",
	"APP_SECRET",
	"APPLICATION_KEY",
	"SECRET_KEY",
	"SECRET_TOKEN",
	"PRIVATE_KEY",
	"PUBLIC_KEY",
	"ACCESS_KEY",
	"ACCESS_TOKEN",
	"ACCESS_SECRET",
	"AUTH_TOKEN",
	"AUTH_SECRET",
	"AUTH_KEY",
	"BEARER_TOKEN",
	"JWT_SECRET",
	"JWT_KEY",
	"JWT_PRIVATE_KEY",
	"JWT_SIGNING_KEY",
	"TOKEN_SECRET",
	"ACCESS_TOKEN_SECRET",
	"REFRESH_TOKEN_SECRET",
	"ENCRYPTION_KEY",
	"SIGNING_KEY",
	"MASTER_KEY",
	"APP_SECRET_KEY",
	"SESSION_SECRET",
	"COOKIE_SECRET",
	"CSRF_SECRET",
	"HASH_SALT",
	"SALT",

	// Payment
	"STRIPE_SECRET",
	"STRIPE_SECRET_KEY",
	"STRIPE_PUBLISHABLE_KEY",
	"STRIPE_WEBHOOK_SECRET",
	"STRIPE_API_KEY",
	"PAYPAL_CLIENT_ID",
	"PAYPAL_CLIENT_SECRET",
	"PAYPAL_SECRET",
	"RAZORPAY_KEY",
	"RAZORPAY_SECRET",
	"SQUARE_ACCESS_TOKEN",
	"BRAINTREE_PRIVATE_KEY",
	"BRAINTREE_MERCHANT_ID",
	"COINBASE_API_KEY",

	// Email / SMS / Communication
	"SENDGRID_API_KEY",
	"SENDGRID_KEY",
	"MAILGUN_API_KEY",
	"MAILGUN_SECRET",
	"MAILGUN_DOMAIN",
	"MAILCHIMP_API_KEY",
	"POSTMARK_API_KEY",
	"POSTMARK_TOKEN",
	"SES_ACCESS_KEY",
	"SMTP_PASSWORD",
	"SMTP_USERNAME",
	"SMTP_HOST",
	"MAIL_PASSWORD",
	"MAIL_USERNAME",
	"EMAIL_PASSWORD",
	"TWILIO_AUTH_TOKEN",
	"TWILIO_ACCOUNT_SID",
	"TWILIO_API_KEY",
	"TWILIO_API_SECRET",
	"VONAGE_API_KEY",
	"NEXMO_API_KEY",
	"NEXMO_API_SECRET",
	"PUSHER_APP_ID",
	"PUSHER_KEY",
	"PUSHER_SECRET",
	"PUSHER_APP_KEY",

	// Auth providers
	"CLERK_SECRET_KEY",
	"CLERK_API_KEY",
	"CLERK_PUBLISHABLE_KEY",
	"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
	"AUTH0_SECRET",
	"AUTH0_CLIENT_ID",
	"AUTH0_CLIENT_SECRET",
	"AUTH0_DOMAIN",
	"OKTA_CLIENT_SECRET",
	"OKTA_API_TOKEN",
	"COGNITO_USER_POOL_ID",
	"COGNITO_CLIENT_SECRET",
	"NEXTAUTH_SECRET",
	"NEXTAUTH_URL",
	"NEXT_AUTH_SECRET",
	"OAUTH_CLIENT_SECRET",
	"OAUTH_CLIENT_ID",
	"GOOGLE_CLIENT_SECRET",
	"FACEBOOK_APP_SECRET",
	"FACEBOOK_CLIENT_SECRET",
	"TWITTER_API_KEY",
	"TWITTER_API_SECRET",
	"TWITTER_BEARER_TOKEN",
	"DISCORD_CLIENT_SECRET",
	"DISCORD_BOT_TOKEN",
	"DISCORD_TOKEN",

	// Source control / CI
	"GITHUB_TOKEN",
	"GITHUB_SECRET",
	"GITHUB_CLIENT_SECRET",
	"GITHUB_APP_PRIVATE_KEY",
	"GH_TOKEN",
	"GITLAB_TOKEN",
	"GITLAB_PRIVATE_TOKEN",
	"BITBUCKET_SECRET",
	"BITBUCKET_TOKEN",
	"CIRCLECI_TOKEN",
	"TRAVIS_TOKEN",
	"JENKINS_TOKEN",
	"JENKINS_PASSWORD",
	"NPM_TOKEN",
	"NPM_AUTH_TOKEN",
	"PYPI_TOKEN",
	"PYPI_PASSWORD",
	"DOCKER_PASSWORD",
	"DOCKER_TOKEN",
	"DOCKERHUB_TOKEN",
	"REGISTRY_PASSWORD",

	// Messaging / Collaboration
	"SLACK_TOKEN",
	"SLACK_WEBHOOK",
	"SLACK_WEBHOOK_URL",
	"SLACK_BOT_TOKEN",
	"SLACK_SIGNING_SECRET",
	"SLACK_API_TOKEN",
	"TELEGRAM_BOT_TOKEN",
	"TELEGRAM_TOKEN",
	"TEAMS_WEBHOOK",

	// CDN / Storage / Media
	"CLOUDINARY_URL",
	"CLOUDINARY_API_KEY",
	"CLOUDINARY_API_SECRET",
	"CLOUDFLARE_API_KEY",
	"CLOUDFLARE_API_TOKEN",
	"CLOUDFLARE_ZONE_ID",
	"IMGIX_TOKEN",
	"UPLOADTHING_SECRET",
	"UPLOADTHING_TOKEN",

	// Search / AI / ML
	"OPENAI_API_KEY",
	"OPENAI_KEY",
	"ANTHROPIC_API_KEY",
	"COHERE_API_KEY",
	"GEMINI_API_KEY",
	"GOOGLE_AI_API_KEY",
	"GOOGLE_GENERATIVE_AI_KEY",
	"HUGGINGFACE_TOKEN",
	"REPLICATE_API_TOKEN",
	"GROQ_API_KEY",
	"MISTRAL_API_KEY",
	"TOGETHER_API_KEY",
	"PERPLEXITY_API_KEY",
	"DEEPSEEK_API_KEY",
	"BASE44_API_KEY",
	"REPLIT_TOKEN",
	"REPLIT_DB_URL",
	"PINECONE_API_KEY",
	"PINECONE_ENVIRONMENT",
	"WEAVIATE_API_KEY",
	"QDRANT_API_KEY",
	"ALGOLIA_API_KEY",
	"ALGOLIA_APP_ID",
	"ALGOLIA_ADMIN_KEY",
	"ALGOLIA_SEARCH_KEY",
	"ELASTICSEARCH_URL",
	"ELASTICSEARCH_PASSWORD",
	"MEILISEARCH_KEY",
	"TYPESENSE_API_KEY",

	// Monitoring / Analytics / Error tracking
	"SENTRY_DSN",
	"SENTRY_AUTH_TOKEN",
	"DATADOG_API_KEY",
	"DATADOG_APP_KEY",
	"NEW_RELIC_LICENSE_KEY",
	"NEWRELIC_KEY",
	"BUGSNAG_API_KEY",
	"ROLLBAR_ACCESS_TOKEN",
	"LOGROCKET_APP_ID",
	"MIXPANEL_TOKEN",
	"AMPLITUDE_API_KEY",
	"SEGMENT_WRITE_KEY",
	"HEAP_API_KEY",
	"POSTHOG_API_KEY",
	"PLAUSIBLE_API_KEY",
	"GOOGLE_ANALYTICS_ID",
	"GA_TRACKING_ID",
	"GTM_ID",

	// Maps / Location
	"GOOGLE_MAPS_API_KEY",
	"GOOGLE_MAPS_KEY",
	"MAPBOX_ACCESS_TOKEN",
	"MAPBOX_TOKEN",
	"HERE_API_KEY",

	// Vercel / Netlify / Hosting
	"VERCEL_TOKEN",
	"VERCEL_ORG_ID",
	"VERCEL_PROJECT_ID",
	"NETLIFY_AUTH_TOKEN",
	"NETLIFY_SITE_ID",
	"HEROKU_API_KEY",
	"HEROKU_APP_NAME",
	"FLY_API_TOKEN",
	"RAILWAY_TOKEN",
	"RENDER_API_KEY",
	"DIGITAL_OCEAN_TOKEN",

	// CMS
	"CONTENTFUL_ACCESS_TOKEN",
	"CONTENTFUL_SPACE_ID",
	"CONTENTFUL_MANAGEMENT_TOKEN",
	"STRAPI_TOKEN",
	"SANITY_TOKEN",
	"SANITY_PROJECT_ID",
	"PRISMIC_ACCESS_TOKEN",
	"GHOST_API_KEY",
	"GHOST_ADMIN_API_KEY",
	"DIRECTUS_TOKEN",
	"WORDPRESS_AUTH_KEY",
	"WORDPRESS_SECURE_AUTH_KEY",

	// Crypto / Blockchain
	"ALCHEMY_API_KEY",
	"INFURA_API_KEY",
	"INFURA_PROJECT_SECRET",
	"ETHERSCAN_API_KEY",
	"MORALIS_API_KEY",
	"WALLET_PRIVATE_KEY",
	"MNEMONIC",
	"SEED_PHRASE",

	// AI coding tools / MCP
	"MCP_SERVER_TOKEN",
	"MCP_API_KEY",
	"DEVIN_API_KEY",
	"CURSOR_API_KEY",
	"REPLIT_API_KEY",
	"BOLT_API_KEY",
	"LOVABLE_API_KEY",
	"V0_API_KEY",
	"TEMPO_API_KEY",
	"SOFTGEN_API_KEY",
	"WINDSURF_TOKEN",
	"CODEIUM_API_KEY",
	"TABNINE_API_KEY",
	"AMAZON_Q_API_KEY",
	"CODEX_API_KEY",

	// Generic sensitive terms
	"password",
	"passwd",
	"pwd",
	"secret",
	"credential",
	"token",
	"apikey",
	"api_key",
	"private_key",
	"secret_key",
	"access_key",
	"client_secret",
	"auth_token",
	"bearer",
	"authorization",

	// Connection strings
	"mongodb://",
	"mongodb+srv://",
	"postgres://",
	"postgresql://",
	"mysql://",
	"redis://",
	"rediss://",
	"amqp://",
	"amqps://",
	"smtp://",
	"ftp://",
	"sftp://",
	"ssh://",
	"Server=",
	"Data Source=",
	"jdbc:",

	// Git config indicators
	"[core]",
	"repositoryformatversion",
	"[remote \"origin\"]",

	// Key format indicators
	"-----BEGIN RSA PRIVATE KEY-----",
	"-----BEGIN PRIVATE KEY-----",
	"-----BEGIN EC PRIVATE KEY-----",
	"-----BEGIN OPENSSH PRIVATE KEY-----",
	"-----BEGIN DSA PRIVATE KEY-----",
	"-----BEGIN PGP PRIVATE KEY BLOCK-----",
	"-----BEGIN CERTIFICATE-----",

	// Common framework secrets
	"APP_KEY=base64:",
	"LARAVEL_KEY",
	"DJANGO_SECRET_KEY",
	"FLASK_SECRET_KEY",
	"RAILS_MASTER_KEY",
	"RAILS_SECRET_KEY_BASE",
	"SECRET_KEY_BASE",
	"DEVISE_SECRET_KEY",
	"SPRING_DATASOURCE_PASSWORD",
	"SPRING_SECURITY_SECRET",
}

// ---------------------------------------------------------------------------
// DATA: Regex patterns for context-aware matching
// ---------------------------------------------------------------------------

var huntrEnvKeyValuePattern = regexp.MustCompile(`(?m)^[A-Z_][A-Z0-9_]*=.+$`)
var huntrGitConfigPattern = regexp.MustCompile(`(?m)^\[(core|remote|branch|user)(\s|])`)

// ---------------------------------------------------------------------------
// DATA: Content-type whitelist and HTML indicators
// ---------------------------------------------------------------------------

var huntrAcceptedContentTypes = []string{
	"text/plain",
	"application/json",
	"application/octet-stream",
	"application/zip",
	"application/sql",
	"application/xml",
	"text/xml",
	"application/x-sql",
}

var huntrHTMLIndicators = []string{
	"<!doctype",
	"<html",
	"<head>",
	"<body",
	"<meta",
	"<script",
}

// ---------------------------------------------------------------------------
// DATA: Generic words skipped by specific pattern matcher
// ---------------------------------------------------------------------------

var huntrGenericWords = map[string]bool{
	"PASSWORD":                true,
	"PASSWD":                  true,
	"PWD":                     true,
	"SECRET":                  true,
	"CREDENTIAL":              true,
	"TOKEN":                   true,
	"APIKEY":                  true,
	"API_KEY":                 true,
	"PRIVATE_KEY":             true,
	"SECRET_KEY":              true,
	"ACCESS_KEY":              true,
	"CLIENT_SECRET":           true,
	"AUTH_TOKEN":              true,
	"BEARER":                  true,
	"AUTHORIZATION":           true,
	"[CORE]":                  true,
	"REPOSITORYFORMATVERSION": true,
}

// ===========================================================================
// VALIDATION FUNCTIONS
// ===========================================================================

// huntrIsValidContentType checks if the HTTP response Content-Type indicates
// a real credential file (not an HTML error page). Empty is allowed since
// some servers omit the header entirely.
func huntrIsValidContentType(path, contentType string) bool {
	if contentType == "" {
		return true
	}
	ct := strings.ToLower(contentType)
	if before, _, found := strings.Cut(ct, ";"); found {
		ct = strings.TrimSpace(before)
	}
	if ct == "text/html" {
		return false
	}
	for _, accepted := range huntrAcceptedContentTypes {
		if ct == accepted {
			return true
		}
	}
	return false
}

// huntrContainsHTMLIndicators returns true if body looks like an HTML page
// (error page, login page, etc.) rather than a raw credential file.
func huntrContainsHTMLIndicators(body string) bool {
	bodyLower := strings.ToLower(body)
	for _, indicator := range huntrHTMLIndicators {
		if strings.Contains(bodyLower, indicator) {
			return true
		}
	}
	return false
}

// huntrMatchPatternsContextAware does smart .env and .git detection.
// .env requires 3+ KEY=VALUE lines; .git requires INI section headers.
func huntrMatchPatternsContextAware(body string) []string {
	var matches []string
	envMatches := huntrEnvKeyValuePattern.FindAllString(body, -1)
	if len(envMatches) >= 3 {
		matches = append(matches, ".env-exposed")
	}
	if huntrGitConfigPattern.MatchString(body) {
		matches = append(matches, ".git-exposed")
	}
	return matches
}

// huntrMatchSpecificPatterns checks for specific credential key patterns
// using KEY=VALUE format matching. Skips generic words.
func huntrMatchSpecificPatterns(body string) []string {
	var matched []string
	bodyUpper := strings.ToUpper(body)
	for _, pattern := range huntrCredentialPatterns {
		patternUpper := strings.ToUpper(pattern)
		if huntrGenericWords[patternUpper] {
			continue
		}
		if strings.Contains(bodyUpper, patternUpper+"=") {
			matched = append(matched, pattern)
		}
	}
	return matched
}

// huntrIsSoft404 determines if a response is a soft 404 by comparing body
// length against the homepage baseline. Uses 5% threshold.
func huntrIsSoft404(bodyLen int, baselineLen int) bool {
	if baselineLen == 0 {
		return false
	}
	diff := bodyLen - baselineLen
	if diff < 0 {
		diff = -diff
	}
	threshold := baselineLen * 5 / 100
	return diff <= threshold
}

// huntrClassifySeverity assigns a severity level based on the path.
func huntrClassifySeverity(path string, patterns []string) string {
	// Critical: private keys, env files with real creds, database dumps
	switch {
	case strings.HasPrefix(path, "/.env") && len(patterns) > 0:
		return "critical"
	case strings.Contains(path, "id_rsa") || strings.Contains(path, "id_ed25519"):
		return "critical"
	case strings.HasSuffix(path, ".key") || strings.HasSuffix(path, ".pem"):
		return "critical"
	case strings.HasSuffix(path, ".sql") || strings.HasSuffix(path, ".sqlite") || strings.HasSuffix(path, ".sqlite3"):
		return "critical"
	case strings.Contains(path, "wp-config"):
		return "critical"
	case strings.Contains(path, "credentials") || strings.Contains(path, "secrets"):
		return "critical"
	case strings.Contains(path, "master.key"):
		return "critical"
	case strings.Contains(path, "terraform.tfstate"):
		return "critical"
	case strings.Contains(path, ".aws/credentials"):
		return "critical"
	}

	// High: git exposure, config files with patterns, database configs
	switch {
	case strings.HasPrefix(path, "/.git/"):
		return "high"
	case strings.Contains(path, "config") && len(patterns) > 0:
		return "high"
	case strings.Contains(path, "actuator"):
		return "high"
	case strings.Contains(path, "phpinfo"):
		return "high"
	case strings.Contains(path, ".htpasswd"):
		return "high"
	case strings.Contains(path, "docker-compose"):
		return "high"
	case strings.Contains(path, "backup") && (strings.HasSuffix(path, ".zip") || strings.HasSuffix(path, ".tar.gz")):
		return "high"
	}

	// Medium: source maps, package files, admin panels
	switch {
	case strings.HasSuffix(path, ".map"):
		return "medium"
	case strings.Contains(path, "admin") || strings.Contains(path, "dashboard"):
		return "medium"
	case strings.Contains(path, "swagger") || strings.Contains(path, "graphql"):
		return "medium"
	case path == "/package.json" || path == "/composer.json":
		return "medium"
	case strings.Contains(path, ".log"):
		return "medium"
	}

	// Default: info
	if len(patterns) > 0 {
		return "high"
	}
	return "info"
}

// ===========================================================================
// SCAN ENGINE
// ===========================================================================

// huntrGetBaseline fetches the homepage of a domain to get its content length
// for soft 404 detection. Tries HTTPS first, falls back to HTTP.
func huntrGetBaseline(ctx context.Context, client *http.Client, domain string) int {
	schemes := []string{"https", "http"}
	for _, scheme := range schemes {
		baseURL := fmt.Sprintf("%s://%s/", scheme, domain)
		req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
		if err != nil {
			continue
		}
		req.Header.Set("User-Agent", UserAgent)
		req.Header.Set("Accept", "*/*")

		resp, err := client.Do(req)
		if err != nil {
			continue
		}
		bodyBuf := make([]byte, 50*1024)
		n, _ := io.ReadFull(resp.Body, bodyBuf)
		resp.Body.Close()
		return n
	}
	return 0
}

// huntrCheckPath probes a single URL path on a domain. Returns whether the
// path is exposed, plus metadata about the response.
//
// Logic: HTTPS first with HTTP fallback, 3 retries on 429/5xx, 50KB body
// limit, and 4-stage validation pipeline:
//   Stage 1: Content-Type filter (reject text/html)
//   Stage 2: HTML body rejection (detect error pages)
//   Stage 3: Soft 404 detection (compare to baseline)
//   Stage 4: Context-aware credential pattern matching
func huntrCheckPath(ctx context.Context, client *http.Client, domain, path string, baselineLen int) (exposed bool, statusCode int, contentType string, bodySnippet string, patterns []string, err error) {
	schemes := []string{"https", "http"}

	for _, scheme := range schemes {
		fullURL := fmt.Sprintf("%s://%s%s", scheme, domain, path)

		var lastErr error
		for attempt := 0; attempt < 3; attempt++ {
			select {
			case <-ctx.Done():
				return false, 0, "", "", nil, ctx.Err()
			default:
			}

			req, reqErr := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
			if reqErr != nil {
				lastErr = reqErr
				break
			}
			req.Header.Set("User-Agent", UserAgent)
			req.Header.Set("Accept", "*/*")

			resp, doErr := client.Do(req)
			if doErr != nil {
				lastErr = doErr
				break // connection error, try next scheme
			}

			// Retry on 429 / 5xx
			switch resp.StatusCode {
			case 429:
				resp.Body.Close()
				retryDelay := 5 * time.Second
				if ra := resp.Header.Get("Retry-After"); ra != "" {
					if sec, pErr := strconv.Atoi(ra); pErr == nil {
						retryDelay = time.Duration(sec) * time.Second
					}
				}
				select {
				case <-ctx.Done():
					return false, 0, "", "", nil, ctx.Err()
				case <-time.After(retryDelay):
					continue
				}
			case 502, 503, 504:
				resp.Body.Close()
				select {
				case <-ctx.Done():
					return false, 0, "", "", nil, ctx.Err()
				case <-time.After(5 * time.Second):
					continue
				}
			}

			// Read body (50KB limit)
			bodyBuf := make([]byte, 50*1024)
			n, _ := io.ReadFull(resp.Body, bodyBuf)
			resp.Body.Close()
			body := string(bodyBuf[:n])

			statusCode = resp.StatusCode
			contentType = resp.Header.Get("Content-Type")

			// Only 200 responses can be exposures
			if resp.StatusCode != 200 {
				return false, statusCode, contentType, "", nil, nil
			}

			// Stage 1: Content-Type validation
			if !huntrIsValidContentType(path, contentType) {
				return false, statusCode, contentType, "", nil, nil
			}

			// Stage 2: HTML body rejection
			if huntrContainsHTMLIndicators(body) {
				return false, statusCode, contentType, "", nil, nil
			}

			// Stage 3: Soft 404 detection
			if huntrIsSoft404(len(body), baselineLen) {
				return false, statusCode, contentType, "", nil, nil
			}

			// Stage 4: Context-aware pattern matching
			matched := huntrMatchPatternsContextAware(body)
			specificMatched := huntrMatchSpecificPatterns(body)
			matched = append(matched, specificMatched...)

			if len(matched) > 0 {
				snippet := body
				if len(snippet) > 500 {
					snippet = snippet[:500]
				}
				return true, statusCode, contentType, snippet, matched, nil
			}

			return false, statusCode, contentType, "", nil, nil
		}

		if lastErr != nil {
			continue // try next scheme
		}
	}

	return false, 0, "", "", nil, fmt.Errorf("failed to connect to %s", domain)
}

// ===========================================================================
// TUI SCAN METHODS
// ===========================================================================

// runHuntrScan scans a single domain against all credential paths.
func (tui *TUI) runHuntrScan(ctx context.Context, domain string) {
	tui.startLog("huntr-single")
	defer tui.closeLog()

	// Clean domain
	domain = strings.TrimPrefix(domain, "http://")
	domain = strings.TrimPrefix(domain, "https://")
	domain = strings.TrimRight(domain, "/")

	totalPaths := int64(len(huntrCredentialPaths))
	atomic.StoreInt64(&tui.huntrTotal, totalPaths)
	atomic.StoreInt64(&tui.huntrChecked, 0)
	atomic.StoreInt64(&tui.huntrFindings, 0)
	tui.huntrCurrentDomain = domain

	tui.addOutput(fmt.Sprintf("[*] HUNTR SCAN -- Target: %s", domain))
	tui.addOutput(fmt.Sprintf("[*] Checking %d credential paths...", totalPaths))
	tui.writeLog("Starting Huntr scan on %s (%d paths)", domain, totalPaths)

	// Create HTTP client
	transport := &http.Transport{
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
		MaxIdleConns:          200,
		MaxIdleConnsPerHost:   50,
		MaxConnsPerHost:       50,
		IdleConnTimeout:       30 * time.Second,
		DisableKeepAlives:     false,
		ResponseHeaderTimeout: tui.huntrTimeout,
		TLSHandshakeTimeout:   5 * time.Second,
		DialContext: (&net.Dialer{
			Timeout:   tui.huntrTimeout,
			KeepAlive: 30 * time.Second,
		}).DialContext,
	}
	client := &http.Client{
		Timeout:   tui.huntrTimeout,
		Transport: transport,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			if len(via) >= 3 {
				return http.ErrUseLastResponse
			}
			return nil
		},
	}

	// Get baseline for soft 404 detection
	tui.addOutput("[*] Fetching homepage baseline for soft 404 detection...")
	baselineLen := huntrGetBaseline(ctx, client, domain)
	if baselineLen > 0 {
		tui.addOutput(fmt.Sprintf("[*] Baseline: %d bytes", baselineLen))
	} else {
		tui.addOutput("[!] No baseline obtained -- soft 404 detection disabled")
	}

	startTime := time.Now()
	findingsForDomain := 0

	// Worker pool using semaphore pattern
	sem := make(chan struct{}, tui.huntrWorkers)
	var wg sync.WaitGroup
	var mu sync.Mutex

	for _, path := range huntrCredentialPaths {
		select {
		case <-ctx.Done():
			tui.addOutput("[!] Scan cancelled")
			tui.writeLog("Scan cancelled by user")
			goto done
		default:
		}

		sem <- struct{}{}
		wg.Add(1)

		go func(p string) {
			defer wg.Done()
			defer func() { <-sem }()

			exposed, statusCode, ct, snippet, patterns, checkErr := huntrCheckPath(ctx, client, domain, p, baselineLen)
			atomic.AddInt64(&tui.huntrChecked, 1)

			if checkErr != nil {
				return
			}

			if exposed {
				atomic.AddInt64(&tui.huntrFindings, 1)
				severity := huntrClassifySeverity(p, patterns)
				patternsStr := strings.Join(patterns, ", ")
				fullURL := fmt.Sprintf("https://%s%s", domain, p)

				mu.Lock()
				findingsForDomain++
				mu.Unlock()

				// Store in database
				tui.db.conn.Exec(`
					INSERT INTO huntr_findings (domain, path, full_url, status_code, content_type, content_length, body_snippet, matched_patterns, severity, discovered)
					VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
					ON CONFLICT(domain, path) DO UPDATE SET
						status_code = excluded.status_code,
						content_type = excluded.content_type,
						content_length = excluded.content_length,
						body_snippet = excluded.body_snippet,
						matched_patterns = excluded.matched_patterns,
						severity = excluded.severity,
						discovered = CURRENT_TIMESTAMP`,
					domain, p, fullURL, statusCode, ct, len(snippet), snippet, patternsStr, severity)

				// Format output
				severityTag := strings.ToUpper(severity)
				line := fmt.Sprintf("[%s] %s | %d | %s | %s", severityTag, fullURL, statusCode, ct, patternsStr)
				tui.addOutput(line)
				tui.writeLog("FINDING [%s] %s -> %s", severity, fullURL, patternsStr)
			}
		}(path)
	}

done:
	wg.Wait()

	elapsed := time.Since(startTime)
	checked := atomic.LoadInt64(&tui.huntrChecked)
	findings := atomic.LoadInt64(&tui.huntrFindings)

	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("[*] === Scan Complete: %s ===", domain))
	tui.addOutput(fmt.Sprintf("[*] Paths checked: %d/%d", checked, totalPaths))
	tui.addOutput(fmt.Sprintf("[*] Findings: %d", findings))
	tui.addOutput(fmt.Sprintf("[*] Duration: %s", elapsed.Round(time.Millisecond)))
	tui.addOutput("")

	tui.writeLog("Scan complete: %d/%d paths, %d findings, %s", checked, totalPaths, findings, elapsed.Round(time.Millisecond))
}

// runHuntrBulkScan scans multiple domains with a worker pool.
func (tui *TUI) runHuntrBulkScan(ctx context.Context, domains []string) {
	tui.startLog("huntr-bulk")
	defer tui.closeLog()

	totalDomains := int64(len(domains))
	totalPaths := totalDomains * int64(len(huntrCredentialPaths))
	atomic.StoreInt64(&tui.huntrTotal, totalPaths)
	atomic.StoreInt64(&tui.huntrChecked, 0)
	atomic.StoreInt64(&tui.huntrFindings, 0)

	tui.addOutput(fmt.Sprintf("[*] HUNTR BULK SCAN -- %d domains", totalDomains))
	tui.addOutput(fmt.Sprintf("[*] %d paths/domain = %d total checks", len(huntrCredentialPaths), totalPaths))
	tui.addOutput(fmt.Sprintf("[*] Workers: %d | Timeout: %s", tui.huntrWorkers, tui.huntrTimeout))
	tui.writeLog("Bulk scan: %d domains, %d paths each, %d workers", totalDomains, len(huntrCredentialPaths), tui.huntrWorkers)

	// Create shared HTTP client
	transport := &http.Transport{
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
		MaxIdleConns:          500,
		MaxIdleConnsPerHost:   20,
		MaxConnsPerHost:       20,
		IdleConnTimeout:       30 * time.Second,
		DisableKeepAlives:     false,
		ResponseHeaderTimeout: tui.huntrTimeout,
		TLSHandshakeTimeout:   5 * time.Second,
		DialContext: (&net.Dialer{
			Timeout:   tui.huntrTimeout,
			KeepAlive: 30 * time.Second,
		}).DialContext,
	}
	client := &http.Client{
		Timeout:   tui.huntrTimeout,
		Transport: transport,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			if len(via) >= 3 {
				return http.ErrUseLastResponse
			}
			return nil
		},
	}

	startTime := time.Now()

	// Domain-level worker pool (each domain gets all paths)
	domainWorkers := tui.huntrWorkers
	if domainWorkers > int(totalDomains) {
		domainWorkers = int(totalDomains)
	}
	// Use fewer domain workers but let each domain scan its paths concurrently
	domainConcurrency := 10
	if domainConcurrency > domainWorkers {
		domainConcurrency = domainWorkers
	}
	pathsPerDomain := domainWorkers / domainConcurrency
	if pathsPerDomain < 5 {
		pathsPerDomain = 5
	}

	sem := make(chan struct{}, domainConcurrency)
	var wg sync.WaitGroup

	for _, rawDomain := range domains {
		select {
		case <-ctx.Done():
			tui.addOutput("[!] Bulk scan cancelled")
			tui.writeLog("Bulk scan cancelled by user")
			goto bulkDone
		default:
		}

		d := strings.TrimSpace(rawDomain)
		d = strings.TrimPrefix(d, "http://")
		d = strings.TrimPrefix(d, "https://")
		d = strings.TrimRight(d, "/")
		if d == "" {
			continue
		}

		sem <- struct{}{}
		wg.Add(1)

		go func(domain string) {
			defer wg.Done()
			defer func() { <-sem }()

			tui.huntrCurrentDomain = domain

			// Get baseline for this domain
			baselineLen := huntrGetBaseline(ctx, client, domain)

			// Scan all paths for this domain using inner concurrency
			innerSem := make(chan struct{}, pathsPerDomain)
			var innerWg sync.WaitGroup

			for _, path := range huntrCredentialPaths {
				select {
				case <-ctx.Done():
					break
				default:
				}

				innerSem <- struct{}{}
				innerWg.Add(1)

				go func(p string) {
					defer innerWg.Done()
					defer func() { <-innerSem }()

					exposed, statusCode, ct, snippet, patterns, checkErr := huntrCheckPath(ctx, client, domain, p, baselineLen)
					atomic.AddInt64(&tui.huntrChecked, 1)

					if checkErr != nil || !exposed {
						return
					}

					atomic.AddInt64(&tui.huntrFindings, 1)
					severity := huntrClassifySeverity(p, patterns)
					patternsStr := strings.Join(patterns, ", ")
					fullURL := fmt.Sprintf("https://%s%s", domain, p)

					tui.db.conn.Exec(`
						INSERT INTO huntr_findings (domain, path, full_url, status_code, content_type, content_length, body_snippet, matched_patterns, severity, discovered)
						VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
						ON CONFLICT(domain, path) DO UPDATE SET
							status_code = excluded.status_code,
							content_type = excluded.content_type,
							content_length = excluded.content_length,
							body_snippet = excluded.body_snippet,
							matched_patterns = excluded.matched_patterns,
							severity = excluded.severity,
							discovered = CURRENT_TIMESTAMP`,
						domain, p, fullURL, statusCode, ct, len(snippet), snippet, patternsStr, severity)

					severityTag := strings.ToUpper(severity)
					line := fmt.Sprintf("[%s] %s | %d | %s", severityTag, fullURL, statusCode, patternsStr)
					tui.addOutput(line)
					tui.writeLog("FINDING [%s] %s -> %s", severity, fullURL, patternsStr)
				}(path)
			}

			innerWg.Wait()
			tui.addOutput(fmt.Sprintf("[*] Completed: %s", domain))
		}(d)
	}

bulkDone:
	wg.Wait()

	elapsed := time.Since(startTime)
	checked := atomic.LoadInt64(&tui.huntrChecked)
	findings := atomic.LoadInt64(&tui.huntrFindings)

	tui.addOutput("")
	tui.addOutput("[*] === Bulk Scan Complete ===")
	tui.addOutput(fmt.Sprintf("[*] Domains: %d", totalDomains))
	tui.addOutput(fmt.Sprintf("[*] Paths checked: %d/%d", checked, totalPaths))
	tui.addOutput(fmt.Sprintf("[*] Total findings: %d", findings))
	tui.addOutput(fmt.Sprintf("[*] Duration: %s", elapsed.Round(time.Millisecond)))
	tui.addOutput("")

	tui.writeLog("Bulk scan complete: %d domains, %d/%d paths, %d findings, %s",
		totalDomains, checked, totalPaths, findings, elapsed.Round(time.Millisecond))
}

// runHuntrBulkFromFile reads domains from a file and runs bulk scan.
func (tui *TUI) runHuntrBulkFromFile(ctx context.Context, filePath string) {
	file, err := os.Open(filePath)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error opening file: %v", err))
		return
	}
	defer file.Close()

	var domains []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" && !strings.HasPrefix(line, "#") {
			domains = append(domains, line)
		}
	}
	if err := scanner.Err(); err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error reading file: %v", err))
		return
	}

	if len(domains) == 0 {
		tui.addOutput("[-] No domains found in file")
		return
	}

	tui.addOutput(fmt.Sprintf("[*] Loaded %d domains from %s", len(domains), filepath.Base(filePath)))
	tui.runHuntrBulkScan(ctx, domains)
}

// ===========================================================================
// COMMAND HANDLER
// ===========================================================================

// startHuntrCommand routes menu selections for the Huntr module.
func (tui *TUI) startHuntrCommand(cmdKey string) {
	tui.currentCmd = cmdKey
	tui.collectedInputs = make(map[string]string)

	switch cmdKey {
	case "1": // Single domain scan
		tui.inputFields = []string{"domain"}
		tui.addOutput("[*] SCAN -- Enter a domain to scan for credential exposures")
		tui.inputPrompt = "Enter target domain"
		tui.currentField = 0
		tui.inputBuffer = ""
		tui.inputCursor = 0
		tui.commandState = StateInput

	case "2": // Bulk scan from file
		files, _ := filepath.Glob(filepath.Join(tui.listsDir, "*.txt"))
		if len(files) > 0 {
			tui.pickerFiles = nil
			for _, f := range files {
				tui.pickerFiles = append(tui.pickerFiles, f)
			}
			tui.pickerSelected = 0
			tui.commandState = StateFilePicker
			return
		}
		tui.addOutput("[-] No .txt files found in lists/ folder")
		tui.addOutput("[*] Place .txt files (one domain per line) in: " + tui.listsDir)

	case "3": // Stats
		tui.showHuntrStats()

	case "4": // Export CSV
		tui.exportHuntrFindings()

	case "5": // Clear
		tui.clearOutput()
		tui.addOutput("[*] Output cleared")

	case "Q", "q":
		tui.running = false
	}
}

// executeHuntrCommand runs after all input fields are collected.
func (tui *TUI) executeHuntrCommand() {
	tui.commandState = StateRunning
	tui.Render()

	ctx, cancel := context.WithCancel(context.Background())
	tui.cancelScan = cancel

	go func() {
		defer func() { tui.cancelScan = nil }()
		switch tui.currentCmd {
		case "1":
			tui.runHuntrScan(ctx, tui.collectedInputs["domain"])
		}
		if ctx.Err() == nil {
			tui.commandState = StateComplete
		}
		tui.Render()
	}()
}

// huntrHandleInput processes domain input for the Huntr module.
// (Called from the generic text input handler via executeCommand routing.)
func (tui *TUI) huntrHandleInput(input string) {
	input = strings.TrimSpace(input)
	if input == "" {
		return
	}
	tui.collectedInputs["domain"] = input
	tui.executeHuntrCommand()
}

// ===========================================================================
// STATS & EXPORT
// ===========================================================================

// showHuntrStats displays database statistics for Huntr findings.
func (tui *TUI) showHuntrStats() {
	var totalFindings, totalDomains int
	var critCount, highCount, medCount, infoCount int

	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings").Scan(&totalFindings)
	tui.db.conn.QueryRow("SELECT COUNT(DISTINCT domain) FROM huntr_findings").Scan(&totalDomains)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings WHERE severity = 'critical'").Scan(&critCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings WHERE severity = 'high'").Scan(&highCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings WHERE severity = 'medium'").Scan(&medCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings WHERE severity = 'info'").Scan(&infoCount)

	tui.addOutput("")
	tui.addOutput("[*] === Huntr Database Statistics ===")
	tui.addOutput(fmt.Sprintf("    Total findings:  %d", totalFindings))
	tui.addOutput(fmt.Sprintf("    Unique domains:  %d", totalDomains))
	tui.addOutput(fmt.Sprintf("    Credential paths: %d", len(huntrCredentialPaths)))
	tui.addOutput(fmt.Sprintf("    Detection patterns: %d", len(huntrCredentialPatterns)))
	tui.addOutput("")
	tui.addOutput("    --- Severity Breakdown ---")
	tui.addOutput(fmt.Sprintf("    CRITICAL:  %d", critCount))
	tui.addOutput(fmt.Sprintf("    HIGH:      %d", highCount))
	tui.addOutput(fmt.Sprintf("    MEDIUM:    %d", medCount))
	tui.addOutput(fmt.Sprintf("    INFO:      %d", infoCount))

	// Top domains by findings
	rows, err := tui.db.conn.Query(`
		SELECT domain, COUNT(*) as cnt FROM huntr_findings
		GROUP BY domain ORDER BY cnt DESC LIMIT 10`)
	if err == nil {
		defer rows.Close()
		tui.addOutput("")
		tui.addOutput("    --- Top Domains (by findings) ---")
		for rows.Next() {
			var d string
			var c int
			rows.Scan(&d, &c)
			tui.addOutput(fmt.Sprintf("    %-40s %d findings", d, c))
		}
	}

	// Recent findings
	recentRows, err := tui.db.conn.Query(`
		SELECT domain, path, severity, discovered FROM huntr_findings
		ORDER BY discovered DESC LIMIT 10`)
	if err == nil {
		defer recentRows.Close()
		tui.addOutput("")
		tui.addOutput("    --- Recent Findings ---")
		for recentRows.Next() {
			var d, p, sev, disc string
			recentRows.Scan(&d, &p, &sev, &disc)
			tui.addOutput(fmt.Sprintf("    [%s] %s%s (%s)", strings.ToUpper(sev), d, p, disc))
		}
	}
	tui.addOutput("")
}

// exportHuntrFindings exports all findings to CSV.
func (tui *TUI) exportHuntrFindings() {
	var total int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings").Scan(&total)
	if total == 0 {
		tui.addOutput("[-] No findings to export")
		return
	}

	filename := filepath.Join(tui.logsDir, fmt.Sprintf("huntr_export_%s.csv", time.Now().Format("2006-01-02_150405")))
	file, err := os.Create(filename)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error creating export: %v", err))
		return
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// Header
	writer.Write([]string{"domain", "path", "full_url", "status_code", "content_type", "content_length", "matched_patterns", "severity", "discovered"})

	rows, err := tui.db.conn.Query(`
		SELECT domain, path, full_url, status_code, content_type, content_length, matched_patterns, severity, discovered
		FROM huntr_findings ORDER BY severity DESC, domain, path`)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] Error querying: %v", err))
		return
	}
	defer rows.Close()

	count := 0
	for rows.Next() {
		var domain, path, fullURL, ct, patterns, severity, discovered string
		var statusCode, contentLength int
		rows.Scan(&domain, &path, &fullURL, &statusCode, &ct, &contentLength, &patterns, &severity, &discovered)
		writer.Write([]string{
			domain, path, fullURL,
			strconv.Itoa(statusCode), ct, strconv.Itoa(contentLength),
			patterns, severity, discovered,
		})
		count++
	}

	tui.addOutput(fmt.Sprintf("[+] Exported %d findings to %s", count, filename))
}

// ===========================================================================
// DASHBOARD RENDERER
// ===========================================================================

// renderHuntrDashboard draws the Huntr module TUI with crimson theme.
func (tui *TUI) renderHuntrDashboard() {
	modColor := moduleColors[ModuleHuntr] // crimson rgb(220,30,30)
	borderStyle := tcell.StyleDefault.Foreground(modColor)
	titleStyle := tcell.StyleDefault.Foreground(ColorPrimary).Bold(true)
	textStyle := tcell.StyleDefault.Foreground(ColorText)
	highlightStyle := tcell.StyleDefault.Foreground(modColor)
	successStyle := tcell.StyleDefault.Foreground(ColorSuccess)
	dimStyle := tcell.StyleDefault.Foreground(ColorDim)

	menuWidth := 52
	outputX := menuWidth + 1
	outputWidth := tui.width - menuWidth - 2
	inputHeight := 3
	outputHeight := tui.height - inputHeight - 2

	// Header
	var findingsDB int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM huntr_findings").Scan(&findingsDB)
	var domainsDB int
	tui.db.conn.QueryRow("SELECT COUNT(DISTINCT domain) FROM huntr_findings").Scan(&domainsDB)

	header := fmt.Sprintf(" MOTHERFUCKIN TERMINATOR BOT 9000 v%s | MODULE: Huntr ", Version)
	tui.drawString(1, 0, header, titleStyle)

	statsStr := fmt.Sprintf("DB: %d findings | %d domains ", findingsDB, domainsDB)
	tui.drawString(tui.width-len(statsStr)-1, 0, statsStr, dimStyle)

	// Menu box
	tui.drawBox(0, 1, menuWidth, tui.height-inputHeight-1, "COMMANDS", borderStyle)

	menuY := 3
	items := tui.menuItems
	for i, item := range items {
		y := menuY + i
		if y >= tui.height-inputHeight-2 {
			break
		}

		keyStyle := highlightStyle
		nameStyle := textStyle
		if i == tui.selectedItem && !tui.focusOutput {
			tui.fillRect(1, y, menuWidth-2, 1, ' ', tcell.StyleDefault.Background(ColorBorder))
			keyStyle = successStyle.Bold(true).Background(ColorBorder)
			nameStyle = tcell.StyleDefault.Foreground(ColorText).Bold(true).Background(ColorBorder)
		}

		tui.drawString(2, y, fmt.Sprintf("[%s]", item.Key), keyStyle)
		tui.drawStringClipped(6, y, menuWidth-8, item.Name+" - "+item.Desc, nameStyle)
	}

	// Scan info
	infoY := menuY + len(items) + 1
	tui.drawString(2, infoY, "--- Scan Engine ---", dimStyle)
	infoLines := []string{
		fmt.Sprintf(" Paths: %d credential paths", len(huntrCredentialPaths)),
		fmt.Sprintf(" Patterns: %d detection rules", len(huntrCredentialPatterns)),
		" Validation: 4-stage pipeline",
		"   1. Content-Type filter",
		"   2. HTML body rejection",
		"   3. Soft 404 detection",
		"   4. Pattern matching",
		fmt.Sprintf(" Workers: %d | Timeout: %s", tui.huntrWorkers, tui.huntrTimeout),
	}
	for i, line := range infoLines {
		if infoY+1+i < tui.height-inputHeight-2 {
			tui.drawString(2, infoY+1+i, line, tcell.StyleDefault.Foreground(ColorInfo))
		}
	}

	// Progress bar during scans
	if tui.commandState == StateRunning {
		checked := atomic.LoadInt64(&tui.huntrChecked)
		total := atomic.LoadInt64(&tui.huntrTotal)
		findings := atomic.LoadInt64(&tui.huntrFindings)
		currentDomain := tui.huntrCurrentDomain

		progressY := infoY + len(infoLines) + 2
		if progressY < tui.height-inputHeight-2 {
			tui.drawString(2, progressY, "--- Progress ---", dimStyle)

			if progressY+1 < tui.height-inputHeight-2 {
				tui.drawString(2, progressY+1, fmt.Sprintf(" Target: %s", currentDomain), tcell.StyleDefault.Foreground(ColorWarning))
			}

			if progressY+2 < tui.height-inputHeight-2 && total > 0 {
				pct := float64(checked) / float64(total) * 100
				barWidth := menuWidth - 6
				filled := int(float64(barWidth) * float64(checked) / float64(total))
				if filled > barWidth {
					filled = barWidth
				}

				bar := strings.Repeat("\u2588", filled) + strings.Repeat("\u2591", barWidth-filled)
				tui.drawString(2, progressY+2, fmt.Sprintf(" %s %.1f%%", bar, pct), tcell.StyleDefault.Foreground(modColor))
			}

			if progressY+3 < tui.height-inputHeight-2 {
				tui.drawString(2, progressY+3, fmt.Sprintf(" Checked: %d/%d | Findings: %d", checked, total, findings), tcell.StyleDefault.Foreground(ColorInfo))
			}
		}
	}

	// Shortcuts
	shortcutY := tui.height - inputHeight - 6
	if shortcutY > infoY+len(infoLines)+7 {
		tui.drawString(2, shortcutY, "--- Shortcuts ---", dimStyle)
		if shortcutY+1 < tui.height-inputHeight-2 {
			tui.drawString(2, shortcutY+1, "F1: Settings  Tab: Module Switch", tcell.StyleDefault.Foreground(ColorInfo))
		}
		if shortcutY+2 < tui.height-inputHeight-2 {
			tui.drawString(2, shortcutY+2, "ESC: Cancel / Back", tcell.StyleDefault.Foreground(ColorInfo))
		}
		if shortcutY+3 < tui.height-inputHeight-2 {
			tui.drawString(2, shortcutY+3, "PgUp/PgDn: Scroll output", tcell.StyleDefault.Foreground(ColorInfo))
		}
	}

	// Output box
	outputTitle := "OUTPUT"
	outputBorder := borderStyle
	if tui.focusOutput {
		outputTitle = "OUTPUT [FOCUSED]"
		outputBorder = tcell.StyleDefault.Foreground(ColorAccent)
	}
	tui.drawBox(outputX, 1, outputWidth, outputHeight, outputTitle, outputBorder)

	tui.outputMutex.Lock()
	maxLines := outputHeight - 2
	startLine := tui.outputScroll
	for i := 0; i < maxLines && startLine+i < len(tui.outputLines); i++ {
		line := tui.outputLines[startLine+i]
		y := 2 + i

		lineStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite)
		if strings.HasPrefix(line, "[CRITICAL]") {
			lineStyle = tcell.StyleDefault.Foreground(tcell.NewRGBColor(255, 0, 0)).Bold(true)
		} else if strings.HasPrefix(line, "[HIGH]") {
			lineStyle = tcell.StyleDefault.Foreground(tcell.NewRGBColor(255, 100, 30))
		} else if strings.HasPrefix(line, "[MEDIUM]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorWarning)
		} else if strings.HasPrefix(line, "[INFO]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorInfo)
		} else if strings.HasPrefix(line, "[+]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorSuccess)
		} else if strings.HasPrefix(line, "[-]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorDanger)
		} else if strings.HasPrefix(line, "[*]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorInfo)
		} else if strings.HasPrefix(line, "[!]") {
			lineStyle = tcell.StyleDefault.Foreground(ColorSecondary)
		}

		tui.drawStringClipped(outputX+1, y, outputWidth-2, line, lineStyle)
	}

	if len(tui.outputLines) > maxLines {
		scrollInfo := fmt.Sprintf(" %d/%d ", tui.outputScroll+maxLines, len(tui.outputLines))
		tui.drawString(outputX+outputWidth-len(scrollInfo)-1, 1, scrollInfo, dimStyle)
	}
	tui.outputMutex.Unlock()

	// Input box
	inputY := tui.height - inputHeight - 1
	tui.drawBox(0, inputY, tui.width, inputHeight+1, "INPUT", borderStyle)

	promptStyle := tcell.StyleDefault.Foreground(ColorWarning)
	inputStyle := tcell.StyleDefault.Foreground(ColorText)

	if tui.commandState == StateInput {
		prompt := tui.inputPrompt + ": "
		tui.drawString(2, inputY+1, prompt, promptStyle)
		tui.drawString(2+len(prompt), inputY+1, tui.inputBuffer, inputStyle)
		cursorX := 2 + len(prompt) + tui.inputCursor
		if cursorX < tui.width-2 {
			tui.screen.SetContent(cursorX, inputY+1, '\u258C', nil, tcell.StyleDefault.Foreground(modColor))
		}
	} else if tui.commandState == StateRunning {
		checked := atomic.LoadInt64(&tui.huntrChecked)
		total := atomic.LoadInt64(&tui.huntrTotal)
		findings := atomic.LoadInt64(&tui.huntrFindings)
		runMsg := fmt.Sprintf("Scanning... %d/%d paths | %d findings | ESC to cancel", checked, total, findings)
		tui.drawString(2, inputY+1, runMsg, tcell.StyleDefault.Foreground(ColorWarning))
	} else if tui.commandState == StateComplete {
		tui.drawString(2, inputY+1, "Complete! Press any key to continue", successStyle)
	} else {
		hint := "Tab: Module | Arrows: Navigate | Enter: Select | Q: Quit"
		tui.drawStringClipped(2, inputY+1, tui.width-4, hint, dimStyle)
	}

	// Status bar
	statusY := tui.height - 1
	statusColor := modColor
	statusStyle := tcell.StyleDefault.Foreground(tcell.ColorBlack).Background(statusColor)
	tui.fillRect(0, statusY, tui.width, 1, ' ', statusStyle)

	checkedVal := atomic.LoadInt64(&tui.huntrChecked)
	totalVal := atomic.LoadInt64(&tui.huntrTotal)
	findingsVal := atomic.LoadInt64(&tui.huntrFindings)
	status := fmt.Sprintf(" Huntr | Paths: %d/%d | Findings: %d | DB: %d total | Workers: %d ",
		checkedVal, totalVal, findingsVal, findingsDB, tui.huntrWorkers)
	tui.drawString(0, statusY, status, statusStyle)
}
