package main

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"strings"
	"time"
)

// webServer runs the web interface on port 8080
func webServer(shutdown chan struct{}) {
	// Load templates
	tmpl, err := template.ParseFiles("templates/index.html")
	if err != nil {
		logInfo("web", "Error loading templates", map[string]interface{}{
			"error": err.Error(),
		})
		return
	}

	// Create a custom request handler with security headers
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Add security headers
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'")
		w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
		w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")

		// Enforce request method
		if r.Method != http.MethodGet {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}

		// Limit request body size (1MB)
		r.Body = http.MaxBytesReader(w, r.Body, 1<<20)

		// Only serve the index page
		if r.URL.Path != "/" {
			http.NotFound(w, r)
			return
		}

		data, err := getDashboardData()
		if err != nil {
			http.Error(w, "Error fetching data: "+err.Error(), http.StatusInternalServerError)
			return
		}

		err = tmpl.Execute(w, data)
		if err != nil {
			// Check if it's already too late to write headers
			if !isResponseHeaderWritten(err) {
				http.Error(w, "Error rendering template: "+err.Error(), http.StatusInternalServerError)
			} else {
				// Log the error but don't try to write headers again
				logInfo("web", "Template execution error after headers sent", map[string]interface{}{
					"error": err.Error(),
				})
			}
			return
		}
	})

	// Configure server with appropriate timeouts
	server := &http.Server{
		Addr:           ":8080",
		Handler:        handler,
		ReadTimeout:    10 * time.Second, // Time to read the request
		WriteTimeout:   30 * time.Second, // Time to write the response
		IdleTimeout:    60 * time.Second, // Keep-alive connections timeout
		MaxHeaderBytes: 1 << 20,          // 1MB max header size
	}

	// Create a goroutine for the server
	go func() {
		logInfo("web", "Starting web server", map[string]interface{}{
			"port": 8080,
		})

		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logInfo("web", "Web server error", map[string]interface{}{
				"error": err.Error(),
			})
		}
	}()

	// Wait for shutdown signal
	<-shutdown

	// Create a deadline for server shutdown
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Attempt graceful shutdown
	if err := server.Shutdown(ctx); err != nil {
		logInfo("web", "Error during server shutdown", map[string]interface{}{
			"error": err.Error(),
		})
	}

	logInfo("web", "Web server stopped", nil)
}

// getDashboardData fetches the data needed for the dashboard
func getDashboardData() (DashboardData, error) {
	articles := loadArticles()
	now := time.Now()
	hourAgo := now.Add(-60 * time.Minute)

	// Prepare the data structure
	data := DashboardData{
		LastUpdated:   now.Format("Jan 02, 2006 15:04:05 MST"),
		TotalArticles: len(articles),
	}

	// Count broadcast articles, recent articles, and unsummarized articles
	var lastBroadcastTime time.Time
	for _, a := range articles {
		if !a.BroadcastTime.IsZero() && a.BroadcastTime.Unix() > 1 {
			data.TotalBroadcast++

			// Track the most recent broadcast time
			if a.BroadcastTime.After(lastBroadcastTime) {
				lastBroadcastTime = a.BroadcastTime
			}
		}

		if a.FirstSeen.After(hourAgo) {
			data.NewInLastHour++
		}

		if a.Summary == "" || a.Importance == 0 {
			data.UnsummarizedCount++
		}
	}

	// Set the last broadcast time
	data.LastBroadcastTime = lastBroadcastTime

	// Calculate time until next broadcast
	if lastBroadcastTime.IsZero() {
		data.NextBroadcastIn = "As soon as articles are summarized"
	} else {
		nextBroadcastTime := lastBroadcastTime.Add(BROADCAST_INTERVAL)
		if now.After(nextBroadcastTime) {
			// If we're past the interval but haven't broadcast yet,
			// likely waiting for articles to be summarized
			if data.UnsummarizedCount > 0 {
				data.NextBroadcastIn = "Waiting for articles to be summarized"
			} else {
				data.NextBroadcastIn = "Pending (checking every " + BROADCAST_CHECK_INTERVAL.String() + ")"
			}
		} else {
			// We're still within the interval, calculate remaining time
			timeUntilNextBroadcast := nextBroadcastTime.Sub(now)

			// Format as hours and minutes
			hours := int(timeUntilNextBroadcast.Hours())
			minutes := int(timeUntilNextBroadcast.Minutes()) % 60

			if hours > 0 {
				data.NextBroadcastIn = fmt.Sprintf("%dh %dm", hours, minutes)
			} else {
				data.NextBroadcastIn = fmt.Sprintf("%dm", minutes)
			}
		}
	}

	// Get broadcast history (last 100)
	history, err := getBroadcastHistory(100)
	if err != nil {
		return data, err
	}
	data.History = history

	// Get next up articles (importance sorted, less than 24 hours old)
	nextUp, err := getNextUpArticles()
	if err != nil {
		return data, err
	}
	data.NextUp = nextUp

	// Get recent logs
	recentLogs, err := getRecentLogs(100)
	if err != nil {
		logInfo("web", "Error fetching recent logs", map[string]interface{}{
			"error": err.Error(),
		})
		// Continue with empty logs list
		recentLogs = []LogEntry{}
	}
	data.RecentLogs = recentLogs

	return data, nil
}

// isResponseHeaderWritten checks if an error indicates headers were already written
func isResponseHeaderWritten(err error) bool {
	// Check common patterns that indicate the response was already partially written
	errStr := err.Error()
	return strings.Contains(errStr, "write: broken pipe") ||
		strings.Contains(errStr, "write: connection reset by peer") ||
		strings.Contains(errStr, "http: superfluous response.WriteHeader")
}