package dorks

import (
	"context"
	"errors"
	"fmt"
)

// ErrSourceNotImplemented is returned by Runner.Run when no executor has
// been registered for a dork's source. Most sources arrive in Phase 9-16
// (OSINT) — this phase only wires the GitHub executor live (Plan 08-05).
var ErrSourceNotImplemented = errors.New("dork source not yet implemented")

// ErrMissingAuth is returned by an Executor when the source requires
// credentials (e.g. GITHUB_TOKEN) that are not configured.
var ErrMissingAuth = errors.New("dork source requires auth credentials")

// Match is a single hit produced by an Executor. Downstream code feeds
// Snippet into the engine detection pipeline.
type Match struct {
	DorkID  string
	Source  string
	URL     string
	Snippet string // content chunk forwarded to the detector
	Path    string // file path inside the source (repo, URL, etc.)
}

// Executor runs a single dork against its source backend. Implementations
// live in per-source files (executor_github.go, executor_shodan.go, ...).
// All executors except GitHub are stubs in Phase 8 and return
// ErrSourceNotImplemented.
type Executor interface {
	// Source returns the dork source identifier this executor handles,
	// e.g. "github". Must match one of ValidSources.
	Source() string
	// Execute runs the dork and returns matches. limit bounds the number
	// of results to request from the backend (zero means backend default).
	Execute(ctx context.Context, d Dork, limit int) ([]Match, error)
}

// Runner dispatches dorks to the correct per-source Executor.
type Runner struct {
	executors map[string]Executor
}

// NewRunner returns an empty Runner. Call Register to wire in per-source
// executors. In Phase 8, only the GitHub executor is registered by
// Plan 08-05; every other source returns ErrSourceNotImplemented.
func NewRunner() *Runner {
	return &Runner{executors: make(map[string]Executor)}
}

// Register installs an Executor, replacing any prior executor for the same
// source.
func (r *Runner) Register(e Executor) {
	r.executors[e.Source()] = e
}

// Executor returns the registered Executor for the given source along with a
// boolean indicating whether one was found.
func (r *Runner) Executor(source string) (Executor, bool) {
	e, ok := r.executors[source]
	return e, ok
}

// Run locates the Executor for the dork's source and invokes it. If no
// Executor has been registered, it returns an error wrapping
// ErrSourceNotImplemented.
func (r *Runner) Run(ctx context.Context, d Dork, limit int) ([]Match, error) {
	ex, ok := r.executors[d.Source]
	if !ok {
		return nil, fmt.Errorf("%w: %s (coming Phase 9-16)", ErrSourceNotImplemented, d.Source)
	}
	return ex.Execute(ctx, d, limit)
}
