package rest

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/explorer/backend/api/middleware"
	"github.com/explorer/backend/auth"
	httpmiddleware "github.com/explorer/backend/libs/go-http-middleware"
	"github.com/jackc/pgx/v5/pgxpool"
)

// Server represents the REST API server
type Server struct {
	db         *pgxpool.Pool
	chainID    int
	userAuth   *auth.Auth
	walletAuth *auth.WalletAuth
	jwtSecret  []byte
	aiLimiter  *AIRateLimiter
	aiMetrics  *AIMetrics
}

// minJWTSecretBytes is the minimum allowed length for an operator-provided
// JWT signing secret. 32 random bytes = 256 bits, matching HS256's output.
const minJWTSecretBytes = 32

// defaultDevCSP is the Content-Security-Policy used when CSP_HEADER is unset
// and the server is running outside production. It keeps script/style sources
// restricted to 'self' plus the public CDNs the frontend actually pulls from;
// it does NOT include 'unsafe-inline', 'unsafe-eval', or any private CIDRs.
// Production deployments MUST provide an explicit CSP_HEADER.
const defaultDevCSP = "default-src 'self'; " +
	"script-src 'self' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; " +
	"style-src 'self' https://cdnjs.cloudflare.com; " +
	"font-src 'self' https://cdnjs.cloudflare.com; " +
	"img-src 'self' data: https:; " +
	"connect-src 'self' https://blockscout.defi-oracle.io https://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org; " +
	"frame-ancestors 'none'; " +
	"base-uri 'self'; " +
	"form-action 'self';"

// isProductionEnv reports whether the server is running in production mode.
// Production is signalled by APP_ENV=production or GO_ENV=production.
func isProductionEnv() bool {
	for _, key := range []string{"APP_ENV", "GO_ENV"} {
		if strings.EqualFold(strings.TrimSpace(os.Getenv(key)), "production") {
			return true
		}
	}
	return false
}

// NewServer creates a new REST API server.
//
// Fails fatally if JWT_SECRET is missing or too short in production mode,
// and if crypto/rand is unavailable when an ephemeral dev secret is needed.
func NewServer(db *pgxpool.Pool, chainID int) *Server {
	jwtSecret := loadJWTSecret()
	walletAuth := auth.NewWalletAuth(db, jwtSecret)

	return &Server{
		db:         db,
		chainID:    chainID,
		userAuth:   auth.NewAuth(db),
		walletAuth: walletAuth,
		jwtSecret:  jwtSecret,
		aiLimiter:  NewAIRateLimiter(),
		aiMetrics:  NewAIMetrics(),
	}
}

// loadJWTSecret reads the signing secret from $JWT_SECRET. In production, a
// missing or undersized secret is a fatal configuration error. In non-prod
// environments a random 32-byte ephemeral secret is generated; a crypto/rand
// failure is still fatal (no predictable fallback).
func loadJWTSecret() []byte {
	raw := strings.TrimSpace(os.Getenv("JWT_SECRET"))
	if raw != "" {
		if len(raw) < minJWTSecretBytes {
			log.Fatalf("JWT_SECRET must be at least %d bytes (got %d); refusing to start with a weak signing key",
				minJWTSecretBytes, len(raw))
		}
		return []byte(raw)
	}

	if isProductionEnv() {
		log.Fatal("JWT_SECRET is required in production (APP_ENV=production or GO_ENV=production); refusing to start")
	}

	secret := make([]byte, minJWTSecretBytes)
	if _, err := rand.Read(secret); err != nil {
		log.Fatalf("failed to generate ephemeral JWT secret: %v", err)
	}
	log.Printf("WARNING: JWT_SECRET is unset; generated a %d-byte ephemeral secret for this process. "+
		"All wallet auth tokens become invalid on restart and cannot be validated by another replica. "+
		"Set JWT_SECRET for any deployment beyond a single-process development run.", minJWTSecretBytes)
	return secret
}

// Start starts the HTTP server
func (s *Server) Start(port int) error {
	mux := http.NewServeMux()
	s.SetupRoutes(mux)

	// Initialize auth middleware
	authMiddleware := middleware.NewAuthMiddleware(s.walletAuth)

	// Setup track routes with proper middleware
	s.SetupTrackRoutes(mux, authMiddleware)

	// Security headers. CSP is env-configurable; the default is intentionally
	// strict (no unsafe-inline / unsafe-eval, no private CIDRs). Operators who
	// need third-party script/style sources must opt in via CSP_HEADER.
	csp := strings.TrimSpace(os.Getenv("CSP_HEADER"))
	if csp == "" {
		if isProductionEnv() {
			log.Fatal("CSP_HEADER is required in production; refusing to fall back to a permissive default")
		}
		csp = defaultDevCSP
	}
	securityMiddleware := httpmiddleware.NewSecurity(csp)

	// Add middleware for all routes (outermost to innermost)
	handler := securityMiddleware.AddSecurityHeaders(
		authMiddleware.OptionalAuth( // Optional auth for Track 1, required for others
			s.addMiddleware(
				s.loggingMiddleware(
					s.compressionMiddleware(mux),
				),
			),
		),
	)

	addr := fmt.Sprintf(":%d", port)
	log.Printf("Starting SolaceScan REST API server on %s", addr)
	log.Printf("Tiered architecture enabled: Track 1 (public), Track 2-4 (authenticated)")
	return http.ListenAndServe(addr, handler)
}

// addMiddleware adds common middleware to all routes
func (s *Server) addMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Add branding headers
		w.Header().Set("X-Explorer-Name", "SolaceScan")
		w.Header().Set("X-Explorer-Version", "1.0.0")
		w.Header().Set("X-Powered-By", "SolaceScan")

		// Add CORS headers for API routes (optional: set CORS_ALLOWED_ORIGIN to restrict, e.g. https://blockscout.defi-oracle.io)
		if strings.HasPrefix(r.URL.Path, "/api/") {
			origin := os.Getenv("CORS_ALLOWED_ORIGIN")
			if origin == "" {
				origin = "*"
			}
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-API-Key")

			// Handle preflight
			if r.Method == "OPTIONS" {
				w.WriteHeader(http.StatusOK)
				return
			}
		}

		next.ServeHTTP(w, r)
	})
}

// requireDB returns false and writes 503 if db is nil (e.g. in tests without DB)
func (s *Server) requireDB(w http.ResponseWriter) bool {
	if s.db == nil {
		writeError(w, http.StatusServiceUnavailable, "service_unavailable", "database unavailable")
		return false
	}
	return true
}

// handleListBlocks handles GET /api/v1/blocks
func (s *Server) handleListBlocks(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeMethodNotAllowed(w)
		return
	}
	if !s.requireDB(w) {
		return
	}

	// Validate pagination
	page, pageSize, err := validatePagination(
		r.URL.Query().Get("page"),
		r.URL.Query().Get("page_size"),
	)
	if err != nil {
		writeValidationError(w, err)
		return
	}

	offset := (page - 1) * pageSize

	query := `
		SELECT chain_id, number, hash, timestamp, timestamp_iso, miner, transaction_count, gas_used, gas_limit
		FROM blocks
		WHERE chain_id = $1
		ORDER BY number DESC
		LIMIT $2 OFFSET $3
	`

	// Add query timeout
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	rows, err := s.db.Query(ctx, query, s.chainID, pageSize, offset)
	if err != nil {
		writeInternalError(w, "Database error")
		return
	}
	defer rows.Close()

	blocks := []map[string]interface{}{}
	for rows.Next() {
		var chainID, number, transactionCount int
		var hash, miner string
		var timestamp time.Time
		var timestampISO sql.NullString
		var gasUsed, gasLimit int64

		if err := rows.Scan(&chainID, &number, &hash, &timestamp, &timestampISO, &miner, &transactionCount, &gasUsed, &gasLimit); err != nil {
			continue
		}

		block := map[string]interface{}{
			"chain_id":          chainID,
			"number":            number,
			"hash":              hash,
			"timestamp":         timestamp,
			"miner":             miner,
			"transaction_count": transactionCount,
			"gas_used":          gasUsed,
			"gas_limit":         gasLimit,
		}

		if timestampISO.Valid {
			block["timestamp_iso"] = timestampISO.String
		}

		blocks = append(blocks, block)
	}

	response := map[string]interface{}{
		"data": blocks,
		"meta": map[string]interface{}{
			"pagination": map[string]interface{}{
				"page":      page,
				"page_size": pageSize,
			},
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// handleGetBlock, handleListTransactions, handleGetTransaction, handleGetAddress
// are implemented in blocks.go, transactions.go, and addresses.go respectively

// handleHealth handles GET /health
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("X-Explorer-Name", "SolaceScan")
	w.Header().Set("X-Explorer-Version", "1.0.0")

	// Check database connection
	dbStatus := "ok"
	if s.db != nil {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		if err := s.db.Ping(ctx); err != nil {
			dbStatus = "error: " + err.Error()
		}
	} else {
		dbStatus = "unavailable"
	}

	health := map[string]interface{}{
		"status":    "healthy",
		"timestamp": time.Now().UTC().Format(time.RFC3339),
		"services": map[string]string{
			"database": dbStatus,
			"api":      "ok",
		},
		"chain_id": s.chainID,
		"explorer": map[string]string{
			"name":    "SolaceScan",
			"version": "1.0.0",
		},
	}

	statusCode := http.StatusOK
	if dbStatus != "ok" {
		statusCode = http.StatusServiceUnavailable
		health["status"] = "degraded"
	}

	w.WriteHeader(statusCode)
	json.NewEncoder(w).Encode(health)
}
