// AASD — API Attack Surface Discovery
//
// Version: 2026-04.1
// Booth-ready application for GITEX 2026.
//
// Workflow:
//   Email Input → Domain Discovery (domain-scan) → Filter Subdomains →
//   GoTestWAF Scan → DeepSeek AI Narrative → Static HTML Report (UUID) →
//   Email Report (SMTP button on report page)
package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/skip2/go-qrcode"

	"aasd/internal/ai"
	"aasd/internal/mailer"
	"aasd/internal/scanner"
)

var (
	emailRegex = regexp.MustCompile(`^[^@]+@[^@]+\.[^@]+$`)
	projectDir string
)

func main() {
	// Determine project root (where bin/ and reports/ live)
	var err error
	projectDir, err = os.Getwd()
	if err != nil {
		projectDir = "."
	}

	// Read public base URL from environment (used for QR codes, email links)
	// e.g. AASD_BASE_URL=https://coding.sechpoint.app
	baseURL := os.Getenv("AASD_BASE_URL")

	// Initialize AI client (reads api_key from config.yaml in working directory)
	deepSeekClient, err := ai.NewClient()
	if err != nil {
		fmt.Printf("Warning: AI client init failed: %v (reports will use fallback)\n", err)
		deepSeekClient = nil
	}
	mailerService := mailer.New(mailer.ConfigFromEnv())
	orch := scanner.NewOrchestrator(projectDir, baseURL, deepSeekClient, mailerService)

	// Create main context for graceful shutdown
	mainCtx, cancel := context.WithCancel(context.Background())
	defer cancel()

	router := gin.Default()
	router.LoadHTMLGlob("templates/*")

	// Serve static files
	router.Static("/static", "./static")
	router.StaticFile("/", "./static/index.html")
	router.Static("/reports", "./reports")

	// QR code generator endpoint
	router.GET("/qrcode", func(c *gin.Context) {
		text := c.Query("text")
		if text == "" {
			text = "AASD-2026"
		}
		png, err := qrcode.Encode(text, qrcode.Medium, 256)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate QR code"})
			return
		}
		c.Data(http.StatusOK, "image/png", png)
	})

	// ----- START: Pipeline Initiation -----
	router.POST("/start", func(c *gin.Context) {
		email := strings.TrimSpace(c.PostForm("email"))
		if email == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Email is required"})
			return
		}
		if !emailRegex.MatchString(email) {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email format"})
			return
		}

		parts := strings.Split(email, "@")
		domain := parts[1]
		if !strings.Contains(domain, ".") {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid domain"})
			return
		}
		if strings.ContainsAny(domain, "/\\") {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid domain characters"})
			return
		}
		if len(domain) > 255 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Domain too long"})
			return
		}

		// Start pipeline
		result, err := orch.StartPipeline(mainCtx, email, domain)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to start scan"})
			return
		}

		// Redirect to simulation page with token
		c.Redirect(http.StatusFound, "/simulation?token="+result.ReportToken)
	})

	// ----- STATUS: Poll endpoint -----
	router.GET("/scan-status/:token", func(c *gin.Context) {
		token := c.Param("token")

		// Try token-based lookup first
		if result, exists := orch.GetResult(token); exists {
			c.JSON(http.StatusOK, gin.H{
				"domain":   result.EmailDomain,
				"token":    result.ReportToken,
				"status":   result.Status,
				"subdomains_discovered": len(result.Subdomains),
				"report": result.ReportFile,
			})
			return
		}

		// Fallback: domain-based lookup (for backward compat / simulation page)
		// This supports the old polling mechanism where domain is the key
		domain := c.Param("token") // "token" param could also be a domain
		if result := orch.GetResultByDomain(domain); result != nil {
			c.JSON(http.StatusOK, gin.H{
				"domain":   result.EmailDomain,
				"token":    result.ReportToken,
				"status":   result.Status,
				"subdomains_discovered": len(result.Subdomains),
				"report": result.ReportFile,
			})
			return
		}

		c.JSON(http.StatusNotFound, gin.H{"error": "Scan not found"})
	})

	// ----- ADMIN: Dashboard -----
	router.GET("/admin-dashboard", func(c *gin.Context) {
		results := orch.GetAllResults()
		total, completed, running := orch.CountStats()

		c.HTML(http.StatusOK, "admin.html", gin.H{
			"Results":   results,
			"Total":     total,
			"Completed": completed,
			"Running":   running,
		})
	})

	// ----- EMAIL: Send report via SMTP -----
	router.POST("/email-report", func(c *gin.Context) {
		var req struct {
			Token string `json:"token"`
			Email string `json:"email"`
		}
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
			return
		}

		// If no email provided in payload, try to get it from the scan result
		sendTo := req.Email
		if sendTo == "" {
			result, exists := orch.GetResult(req.Token)
			if !exists {
				c.JSON(http.StatusNotFound, gin.H{"error": "Report not found"})
				return
			}
			sendTo = result.Email
		}

		if !emailRegex.MatchString(sendTo) {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email address"})
			return
		}

		if err := orch.SendEmailReport(req.Token, sendTo); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to send email: %v", err)})
			return
		}

		// Create a report summary page for admin
		reportURL := "/reports/" + req.Token + ".html"

		// Generate QR for the admin to scan (use full URL if baseURL is set)
		qrReportURL := reportURL
		if baseURL != "" {
			qrReportURL = baseURL + reportURL
		}
		qrData := ""
		png, qrErr := qrcode.Encode(qrReportURL, qrcode.Medium, 256)
		if qrErr == nil {
			qrData = "/reports/qr_" + req.Token + ".png"
			os.WriteFile(filepath.Join(projectDir, "reports", "qr_" + req.Token + ".png"), png, 0644)
		}
		_ = qrData

		c.JSON(http.StatusOK, gin.H{
			"message": "Report sent successfully",
			"report":  reportURL,
		})
	})

	// ----- SIMULATION: Serve new simulation page with token support -----
	router.StaticFile("/simulation", "./static/simulation.html")

	// ----- DOWNLOAD: Serve the raw scan results for debugging -----
	router.GET("/report-data/:token", func(c *gin.Context) {
		token := c.Param("token")
		result, exists := orch.GetResult(token)
		if !exists {
			c.JSON(http.StatusNotFound, gin.H{"error": "Report not found"})
			return
		}
		c.JSON(http.StatusOK, result)
	})

	// ----- API: List all scan tokens (for admin integration) -----
	router.GET("/api/scans", func(c *gin.Context) {
		results := orch.GetAllResults()
		type scanSummary struct {
			Token  string `json:"token"`
			Domain string `json:"domain"`
			Status string `json:"status"`
			Time   string `json:"created_at"`
		}
		summaries := make([]scanSummary, 0, len(results))
		for _, r := range results {
			summaries = append(summaries, scanSummary{
				Token:  r.ReportToken,
				Domain: r.EmailDomain,
				Status: string(r.Status),
				Time:   r.CreatedAt.Format(time.RFC3339),
			})
		}
		c.JSON(http.StatusOK, summaries)
	})

	// ----- Signal handling + graceful shutdown -----
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

	srv := &http.Server{
		Addr:    "0.0.0.0:8080",
		Handler: router,
	}

	go func() {
		fmt.Printf("AASD server starting on %s\n", srv.Addr)
		fmt.Printf("Version: 2026-04.1 | API Attack Surface Discovery\n")
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			fmt.Printf("Server error: %v\n", err)
		}
	}()

	<-signalChan
	fmt.Println("\nShutdown signal received, shutting down gracefully...")
	cancel()

	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer shutdownCancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		fmt.Printf("Server shutdown error: %v\n", err)
	}
	fmt.Println("AASD server stopped")
}
