package scanner

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// domainScanResult represents the JSON output from domain-scan tool.
// The domains field is a map where keys are subdomain names.
type domainScanResult struct {
	Domains    map[string]interface{} `json:"domains"`
	Statistics struct {
		TotalDiscovered int `json:"total_subdomains"`
		ActiveHTTP      int `json:"active_services"`
	} `json:"statistics"`
}

// DiscoverSubdomains runs the domain-scan tool against the given domain
// and returns a list of discovered active subdomains.
//
// domain-scan binary must be at projectRoot/domain-scan.
// Uses passive enumeration + TLS certificate analysis + HTTP verification.
// Falls back to the original domain if the scan takes too long or fails.
func DiscoverSubdomains(ctx context.Context, projectRoot, domain string) ([]string, error) {
	binaryPath := filepath.Join(projectRoot, "domain-scan")
	outputFile := filepath.Join(projectRoot, "reports", fmt.Sprintf("discovery_%s.json", strings.ReplaceAll(domain, ".", "_")))

	// Create a context with timeout so discovery doesn't block the pipeline
	discoveryCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
	defer cancel()

	cmd := exec.CommandContext(discoveryCtx, binaryPath,
		"discover",
		domain,
		"--output", outputFile,
		"--format", "json",
		"--timeout", "10",
		"--threads", "10",
		"--quiet",
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		// If the binary fails, return a fallback with just the main domain
		return []string{domain}, fmt.Errorf("domain-scan failed: %v, output: %s", err, string(output))
	}

	// Try to parse the output file
	var result domainScanResult
	if err := readJSONFile(outputFile, &result); err != nil {
		// Try parsing stdout as JSON
		if err := json.Unmarshal(output, &result); err != nil {
			// Fallback: just return the main domain
			return []string{domain}, nil
		}
	}

	if result.Domains == nil || len(result.Domains) == 0 {
		return []string{domain}, nil
	}

	// Extract domain keys from the map
	subdomains := make([]string, 0, len(result.Domains))
	for d := range result.Domains {
		subdomains = append(subdomains, d)
	}

	return subdomains, nil
}

// readJSONFile reads and unmarshals a JSON file.
func readJSONFile(path string, v interface{}) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, v)
}
