package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/joho/godotenv"
)

type server struct {
	pool    *pgxpool.Pool
	mu      sync.Mutex
	seeding bool
}

type seedRequest struct {
	Type     string `json:"type"`
	Password string `json:"password"`
}

type healthResponse struct {
	Status   string `json:"status"`
	Database string `json:"database"`
	Version  string `json:"version"`
	Error    string `json:"error,omitempty"`
}

type seedResponse struct {
	Message string `json:"message"`
	Type    string `json:"type,omitempty"`
}

type rootResponse struct {
	Docs    string `json:"docs"`
	Health  string `json:"health"`
	IP      string `json:"ip"`
	Message string `json:"message"`
	Version string `json:"version"`
}

func main() {
	_ = godotenv.Load()

	databaseURL, err := buildDatabaseURL()
	if err != nil {
		log.Fatalf("❌ Failed to build database URL: %v", err)
	}

	pool, err := pgxpool.New(context.Background(), databaseURL)
	if err != nil {
		log.Fatalf("❌ Failed to create database pool: %v", err)
	}
	defer pool.Close()

	logDatabaseConfig(databaseURL)

	srv := &server{pool: pool}

	mux := http.NewServeMux()
	mux.HandleFunc("/", srv.rootHandler)
	mux.HandleFunc("/health", srv.healthHandler)
	mux.HandleFunc("/seed", srv.seedHandler)
	mux.HandleFunc("/reset", srv.resetHandler)

	handler := withCORS(withLogging(mux))

	port := getenv("PORT", "8080")
	log.Printf("🌱 GoHorseJobs Seeder API listening on port %s", port)
	log.Printf("   Health check: http://localhost:%s/health", port)

	if err := http.ListenAndServe(":"+port, handler); err != nil {
		log.Fatalf("❌ Server failed: %v", err)
	}
}

func (s *server) rootHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	ip := getClientIP(r)
	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "🌱 GoHorseJobs Seeder API is running!",
		"version": "1.0.0",
		"ip":      ip,
		"endpoints": map[string]string{
			"health": "/health",
			"seed":   "POST /seed",
			"reset":  "POST /reset",
		},
	})
}

func (s *server) healthHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeJSON(w, http.StatusMethodNotAllowed, healthResponse{Status: "error", Database: "disconnected", Error: "Method not allowed"})
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()

	var result int
	if err := s.pool.QueryRow(ctx, "SELECT 1").Scan(&result); err != nil {
		log.Printf("Health check failed: %v", err)
		writeJSON(w, http.StatusInternalServerError, healthResponse{Status: "error", Database: "disconnected", Error: err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, healthResponse{Status: "ok", Database: "connected", Version: "1.0.0"})
}

func (s *server) seedHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "Method not allowed"})
		return
	}

	var req seedRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil && err.Error() != "EOF" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON body"})
		return
	}

	if !isAuthorized(req.Password) {
		writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "Unauthorized"})
		return
	}

	if !s.tryStartSeeding() {
		writeJSON(w, http.StatusConflict, map[string]string{"error": "Seeding already in progress"})
		return
	}

	seedType := strings.TrimSpace(req.Type)
	if seedType == "" {
		seedType = "full"
	}

	writeJSON(w, http.StatusOK, seedResponse{Message: "Seeding started", Type: seedType})

	go func() {
		defer s.finishSeeding()
		log.Printf("🚀 Starting manual seed (%s)...", seedType)
		if err := runSeed(seedType); err != nil {
			log.Printf("❌ Manual seed failed: %v", err)
			return
		}
		log.Printf("✅ Manual seed completed")
	}()
}

func (s *server) resetHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "Method not allowed"})
		return
	}

	var req seedRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil && err.Error() != "EOF" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON body"})
		return
	}

	if !isAuthorized(req.Password) {
		writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "Unauthorized"})
		return
	}

	if !s.tryStartSeeding() {
		writeJSON(w, http.StatusConflict, map[string]string{"error": "Seeding/Reset in progress"})
		return
	}

	writeJSON(w, http.StatusOK, seedResponse{Message: "Reset started"})

	go func() {
		defer s.finishSeeding()
		log.Printf("🚀 Starting manual reset...")
		if err := runReset(); err != nil {
			log.Printf("❌ Manual reset failed: %v", err)
			return
		}
		log.Printf("✅ Manual reset completed")
	}()
}

func (s *server) tryStartSeeding() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.seeding {
		return false
	}
	s.seeding = true
	return true
}

func (s *server) finishSeeding() {
	s.mu.Lock()
	s.seeding = false
	s.mu.Unlock()
}

func runSeed(seedType string) error {
	args := []string{"src/index.js"}
	switch seedType {
	case "lite":
		args = append(args, "--lite")
	case "no-locations":
		args = append(args, "--skip-locations")
	case "full":
	default:
		log.Printf("⚠️  Unknown seed type '%s', using full", seedType)
	}

	return runNodeCommand(args...)
}

func runReset() error {
	return runNodeCommand("src/index.js", "--reset")
}

func runNodeCommand(args ...string) error {
	cmd := exec.Command("node", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()
	return cmd.Run()
}

func buildDatabaseURL() (string, error) {
	if databaseURL := strings.TrimSpace(os.Getenv("DATABASE_URL")); databaseURL != "" {
		cleaned := strings.ReplaceAll(databaseURL, "?sslmode=require", "")
		cleaned = strings.ReplaceAll(cleaned, "&sslmode=require", "")
		return cleaned, nil
	}

	host := getenv("DB_HOST", "localhost")
	port := getenv("DB_PORT", "5432")
	user := getenv("DB_USER", "postgres")
	password := getenv("DB_PASSWORD", "postgres")
	name := getenv("DB_NAME", "gohorsejobs")
	sslmode := getenv("DB_SSLMODE", "disable")

	if sslmode == "" {
		sslmode = "disable"
	}

	return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s", url.QueryEscape(user), url.QueryEscape(password), host, port, name, sslmode), nil
}

func logDatabaseConfig(databaseURL string) {
	masked := databaseURL
	if at := strings.Index(masked, "@"); at != -1 {
		masked = "postgres://***@" + masked[at+1:]
	}
	log.Printf("🔌 DB Config: connectionString=%s", masked)
}

func isAuthorized(password string) bool {
	expected := strings.TrimSpace(os.Getenv("SEED_PASSWORD"))
	if expected == "" {
		return true
	}
	return password == expected
}

func withLogging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("[%s] %s %s", time.Now().Format(time.RFC3339), r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	})
}

func withCORS(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func writeJSON(w http.ResponseWriter, status int, payload any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(payload)
}

func getenv(key, fallback string) string {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	return value
}

func getClientIP(r *http.Request) string {
	if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
		parts := strings.Split(forwarded, ",")
		return strings.TrimSpace(parts[0])
	}
	if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
		return realIP
	}
	host := r.RemoteAddr
	if colonPos := strings.LastIndex(host, ":"); colonPos != -1 {
		return host[:colonPos]
	}
	return host
}
