// Package scanner orchestrates the full API Attack Surface Discovery pipeline:
// Email Input → Domain Discovery → Filter Subdomains → GoTestWAF Scan → AI Report.
package scanner

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"aasd/internal/ai"
	"aasd/internal/mailer"
	"aasd/internal/report"
)

// ScanStatus enumerates the pipeline phases.
type ScanStatus string

const (
	StatusPending      ScanStatus = "pending"
	StatusDiscovering  ScanStatus = "discovering"
	StatusScanning     ScanStatus = "scanning"
	StatusGenerating   ScanStatus = "generating"
	StatusCompleted    ScanStatus = "completed"
	StatusFailed       ScanStatus = "failed"
)

// ScanResult holds the full state of a scan pipeline execution.
type ScanResult struct {
	EmailDomain    string     `json:"domain"`
	Email          string     `json:"email"`
	Subdomains     []string   `json:"subdomains,omitempty"`
	CriticalDomain string     `json:"critical_domain,omitempty"`
	Status         ScanStatus `json:"status"`
	ReportToken    string     `json:"token"`
	ReportFile     string     `json:"report_file,omitempty"`
	AIReportFile   string     `json:"ai_report_file,omitempty"`
	Error          string     `json:"error,omitempty"`
	CreatedAt      time.Time  `json:"created_at"`
	CompletedAt    time.Time  `json:"completed_at,omitempty"`
}

// Orchestrator manages the scan pipeline execution.
type Orchestrator struct {
	mu          sync.RWMutex
	results     map[string]*ScanResult // keyed by report token
	projectRoot string
	baseURL     string // public base URL for report links (e.g. https://coding.sechpoint.app)
	aiClient    *ai.Client
	mailer      *mailer.Mailer
}

// NewOrchestrator creates a new scan pipeline orchestrator.
// baseURL is the public-facing base URL used in email report links.
func NewOrchestrator(projectRoot, baseURL string, aiClient *ai.Client, m *mailer.Mailer) *Orchestrator {
	return &Orchestrator{
		results:     make(map[string]*ScanResult),
		projectRoot: projectRoot,
		baseURL:     baseURL,
		aiClient:    aiClient,
		mailer:      m,
	}
}

// StartPipeline kicks off the full discovery pipeline in a goroutine.
// Returns the ScanResult immediately with status "pending".
func (o *Orchestrator) StartPipeline(ctx context.Context, email, domain string) (*ScanResult, error) {
	token, err := report.GenerateToken()
	if err != nil {
		return nil, fmt.Errorf("scanner: failed to generate token: %w", err)
	}

	result := &ScanResult{
		EmailDomain: domain,
		Email:       email,
		Status:      StatusPending,
		ReportToken: token,
		CreatedAt:   time.Now(),
	}

	o.mu.Lock()
	o.results[token] = result
	o.mu.Unlock()

	// Run pipeline in background
	go o.executePipeline(ctx, result)

	return result, nil
}

// executePipeline runs the full pipeline: discover → filter → scan → AI → report.
func (o *Orchestrator) executePipeline(ctx context.Context, result *ScanResult) {
	// Phase 1: Domain Discovery
	o.updateStatus(result.ReportToken, StatusDiscovering)
	subdomains, err := DiscoverSubdomains(ctx, o.projectRoot, result.EmailDomain)
	if err != nil {
		// Continue with just the domain if discovery fails
		subdomains = []string{result.EmailDomain}
	}
	result.Subdomains = subdomains

	// Phase 2: Select most critical subdomain
	criticalDomain := FindMostCriticalSubdomain(subdomains, result.EmailDomain)
	result.CriticalDomain = criticalDomain

	// Phase 3: GoTestWAF Scan
	o.updateStatus(result.ReportToken, StatusScanning)
	reportName := fmt.Sprintf("report_%s", result.ReportToken)
	scanOutput, err := RunGoTestWAF(ctx, o.projectRoot, reportName)
	if err != nil {
		result.Error = fmt.Sprintf("scan error: %v", err)
		scanOutput = fmt.Sprintf("Scan encountered an error: %v\nProceeding with partial results.", err)
	}
	_ = scanOutput // captured for error context; new AI signature accepts consultant report HTML

	// Read GoTestWAF consultant report HTML for AI analysis
	gtwReportPath := filepath.Join(o.projectRoot, "reports", fmt.Sprintf("report_%s.html", result.ReportToken))
	consultantHTML, readErr := os.ReadFile(gtwReportPath)
	if readErr != nil {
		fmt.Printf("scanner: failed to read consultant report %s: %v\n", gtwReportPath, readErr)
	}

	// Phase 4: AI Narrative Generation
	o.updateStatus(result.ReportToken, StatusGenerating)
	var aiHTML string
	if o.aiClient != nil && readErr == nil {
		aiHTML, err = o.aiClient.GenerateResilienceReport(string(consultantHTML))
	}
	if o.aiClient == nil || readErr != nil || err != nil {
		aiHTML = report.GenerateFallbackHTML(subdomains, criticalDomain)
	}

	// Phase 5: Static HTML Report
	reportGen := report.New(o.projectRoot+"/reports", o.baseURL)
	reportPath, err := reportGen.BuildReport(result.ReportToken, result.EmailDomain, aiHTML)
	if err != nil {
		result.Error = fmt.Sprintf("report generation error: %v", err)
		o.updateStatus(result.ReportToken, StatusFailed)
		return
	}

	result.AIReportFile = reportPath
	result.ReportFile = "/reports/" + result.ReportToken + ".html"

	// Inject QR code into GoTestWAF native report (for consultants)
	// gtwReportPath already defined above; reuse it
	if data, err := os.ReadFile(gtwReportPath); err == nil {
		qrURL := fmt.Sprintf("%s/reports/report_%s.html", o.baseURL, result.ReportToken)
		qrBlock := fmt.Sprintf(`
    <!-- Booth QR Code -->
    <div style="text-align:center; margin-top:32px; page-break-before:always;">
      <h2 style="font-size:18px; font-weight:700; margin-bottom:12px;">Scan to access your report</h2>
      <img src="/qrcode?text=%s" alt="QR Code" style="width:200px; height:200px; border:2px solid #ddd; border-radius:8px; padding:8px; background:#fff;" loading="lazy">
      <p style="font-size:12px; color:#666; margin-top:8px;">Show this code to <strong>Sechpoint Aftica Team</strong> at GITEX 2026</p>
    </div>`, qrURL)
		modified := strings.Replace(string(data), "</main>", qrBlock+"\n    </main>", 1)
		if err := os.WriteFile(gtwReportPath, []byte(modified), 0644); err != nil {
			fmt.Printf("scanner: failed to inject QR into GoTestWAF report: %v\n", err)
		}
	} else {
		fmt.Printf("scanner: GoTestWAF report not found for QR injection: %v\n", err)
	}

	result.CompletedAt = time.Now()
	o.updateStatus(result.ReportToken, StatusCompleted)
}

// updateStatus atomically updates the status of a scan result.
func (o *Orchestrator) updateStatus(token string, status ScanStatus) {
	o.mu.Lock()
	defer o.mu.Unlock()
	if r, exists := o.results[token]; exists {
		r.Status = status
	}
}

// GetResult returns the scan result for a given token.
func (o *Orchestrator) GetResult(token string) (*ScanResult, bool) {
	o.mu.RLock()
	defer o.mu.RUnlock()
	r, exists := o.results[token]
	if !exists {
		return nil, false
	}
	cp := *r
	return &cp, true
}

// GetResultByDomain returns the scan result for a given domain (for polling).
func (o *Orchestrator) GetResultByDomain(domain string) *ScanResult {
	o.mu.RLock()
	defer o.mu.RUnlock()
	for _, r := range o.results {
		if r.EmailDomain == domain {
			cp := *r
			return &cp
		}
	}
	return nil
}

// GetAllResults returns all scan results (for admin dashboard).
func (o *Orchestrator) GetAllResults() []ScanResult {
	o.mu.RLock()
	defer o.mu.RUnlock()
	results := make([]ScanResult, 0, len(o.results))
	for _, r := range o.results {
		results = append(results, *r)
	}
	return results
}

// CountStats returns summary statistics for the dashboard.
func (o *Orchestrator) CountStats() (total, completed, running int) {
	o.mu.RLock()
	defer o.mu.RUnlock()
	for _, r := range o.results {
		total++
		switch r.Status {
		case StatusCompleted:
			completed++
		case StatusDiscovering, StatusScanning, StatusGenerating:
			running++
		}
	}
	return
}

// SendEmailReport sends the report link via SMTP.
func (o *Orchestrator) SendEmailReport(token, toEmail string) error {
	result, exists := o.GetResult(token)
	if !exists {
		return fmt.Errorf("report not found for token: %s", token)
	}
	if result.Status != StatusCompleted {
		return fmt.Errorf("report not ready (status: %s)", result.Status)
	}

	reportURL := "/reports/" + token + ".html"
	if o.baseURL != "" {
		reportURL = o.baseURL + reportURL
	}
	return o.mailer.SendReport(toEmail, reportURL)
}
