package web

import (
	"context"
	"embed"
	"encoding/json"
	"fmt"
	"html/template"
	"io/fs"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"git.leaktechnologies.dev/stu/Goondex/internal/config"
	"git.leaktechnologies.dev/stu/Goondex/internal/db"
	import_service "git.leaktechnologies.dev/stu/Goondex/internal/import"
	"git.leaktechnologies.dev/stu/Goondex/internal/model"
	"git.leaktechnologies.dev/stu/Goondex/internal/scraper"
	"git.leaktechnologies.dev/stu/Goondex/internal/search"
	"git.leaktechnologies.dev/stu/Goondex/internal/sync"
)

// ============================================================================
// EMBED STATIC + TEMPLATES
// ============================================================================

//go:embed templates/* static/**/*
var content embed.FS

type Server struct {
	db        *db.DB
	templates *template.Template
	addr      string
	dbPath    string
}

func NewServer(database *db.DB, addr string, dbPath string) (*Server, error) {
	tmpl, err := template.ParseFS(content, "templates/*.html")
	if err != nil {
		return nil, fmt.Errorf("failed to parse templates: %w", err)
	}

	return &Server{
		db:        database,
		templates: tmpl,
		addr:      addr,
		dbPath:    dbPath,
	}, nil
}

func (s *Server) Start() error {
	mux := http.NewServeMux()

	// ============================================================================
	// FIXED STATIC SERVER — THIS IS THE CORRECT WAY
	// ============================================================================

	staticFS, err := fs.Sub(content, "static")
	if err != nil {
		return fmt.Errorf("failed to load embedded static directory: %w", err)
	}

	mux.Handle(
		"/static/",
		http.StripPrefix(
			"/static/",
			http.FileServer(http.FS(staticFS)),
		),
	)

	// ============================================================================
	// ROUTES
	// ============================================================================

	mux.HandleFunc("/", s.handleDashboard)
	mux.HandleFunc("/performers", s.handlePerformerList)
	mux.HandleFunc("/performers/", s.handlePerformerDetail)
	mux.HandleFunc("/studios", s.handleStudioList)
	mux.HandleFunc("/studios/", s.handleStudioDetail)
	mux.HandleFunc("/scenes", s.handleSceneList)
	mux.HandleFunc("/scenes/", s.handleSceneDetail)
	mux.HandleFunc("/movies", s.handleMovieList)
	mux.HandleFunc("/movies/", s.handleMovieDetail)
	mux.HandleFunc("/settings", s.handleSettingsPage)

	// Adult Empire endpoints
	mux.HandleFunc("/api/ae/import/performer", s.handleAEImportPerformer)
	mux.HandleFunc("/api/ae/import/performer-by-url", s.handleAEImportPerformerByURL)
	mux.HandleFunc("/api/ae/import/scene", s.handleAEImportScene)
	mux.HandleFunc("/api/ae/import/scene-by-url", s.handleAEImportSceneByURL)

	// Settings endpoints
	mux.HandleFunc("/api/settings/api-keys", s.handleAPISettingsKeys)
	mux.HandleFunc("/api/settings/database", s.handleAPIDatabase)

	// API
	mux.HandleFunc("/api/import/performer", s.handleAPIImportPerformer)
	mux.HandleFunc("/api/import/studio", s.handleAPIImportStudio)
	mux.HandleFunc("/api/import/scene", s.handleAPIImportScene)

	mux.HandleFunc("/api/import/all", s.handleAPIBulkImportAll)
	mux.HandleFunc("/api/import/all-performers", s.handleAPIBulkImportPerformers)
	mux.HandleFunc("/api/import/all-studios", s.handleAPIBulkImportStudios)
	mux.HandleFunc("/api/import/all-scenes", s.handleAPIBulkImportScenes)

	mux.HandleFunc("/api/import/all-performers/progress", s.handleAPIBulkImportPerformersProgress)
	mux.HandleFunc("/api/import/all-studios/progress", s.handleAPIBulkImportStudiosProgress)
	mux.HandleFunc("/api/import/all-scenes/progress", s.handleAPIBulkImportScenesProgress)

	mux.HandleFunc("/api/sync", s.handleAPISync)
	mux.HandleFunc("/api/sync/status", s.handleAPISyncStatus)

	mux.HandleFunc("/api/search", s.handleAPIGlobalSearch)

	// ============================================================================
	// START SERVER
	// ============================================================================

	fmt.Printf("Starting Goondex Web Server at http://%s\n", s.addr)
	return http.ListenAndServe(s.addr, mux)
}

func (s *Server) render(w http.ResponseWriter, name string, data interface{}) {
	if err := s.templates.ExecuteTemplate(w, name, data); err != nil {
		log.Printf("template render error (%s): %v", name, err)
		http.Error(w, fmt.Sprintf("template render error: %v", err), http.StatusInternalServerError)
	}
}

// ============================================================================
// PAGE HANDLERS
// ============================================================================

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

	performerStore := db.NewPerformerStore(s.db)
	studioStore := db.NewStudioStore(s.db)
	sceneStore := db.NewSceneStore(s.db)
	movieStore := db.NewMovieStore(s.db)

	performers, _ := performerStore.Search("")
	studios, _ := studioStore.Search("")
	scenes, _ := sceneStore.Search("")
	movies, _ := movieStore.Search("")

	data := map[string]interface{}{
		"PageTitle":      "Dashboard",
		"ActivePage":     "dashboard",
		"PerformerCount": len(performers),
		"StudioCount":    len(studios),
		"SceneCount":     len(scenes),
		"MovieCount":     len(movies),
	}

	s.render(w, "dashboard.html", data)
}

func (s *Server) handlePerformerList(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")
	nationalityFilter := r.URL.Query().Get("nationality")
	genderFilter := r.URL.Query().Get("gender")

	store := db.NewPerformerStore(s.db)
	performers, err := store.Search(query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Apply filters
	var filteredPerformers []interface{}
	for _, p := range performers {
		// Filter by nationality if specified
		if nationalityFilter != "" && nationalityFilter != "all" {
			if p.Nationality != nationalityFilter {
				continue
			}
		}

		// Filter by gender if specified
		if genderFilter != "" && genderFilter != "all" {
			if p.Gender != genderFilter {
				continue
			}
		}

		filteredPerformers = append(filteredPerformers, p)
	}

	type PerformerWithCount struct {
		Performer   interface{}
		SceneCount  int
		Age         int
		CountryFlag string
	}

	var performersWithCounts []PerformerWithCount
	for _, p := range filteredPerformers {
		performer := p.(model.Performer)
		count, _ := store.GetSceneCount(performer.ID)

		// Calculate age from birthday
		age := calculateAge(performer.Birthday)

		// Get country flag emoji
		countryFlag := getCountryFlag(performer.Nationality)

		performersWithCounts = append(performersWithCounts,
			PerformerWithCount{
				Performer:   performer,
				SceneCount:  count,
				Age:         age,
				CountryFlag: countryFlag,
			})
	}

	// Get unique nationalities and genders for filter dropdowns
	nationalitiesMap := make(map[string]bool)
	gendersMap := make(map[string]bool)
	for _, p := range performers {
		if p.Nationality != "" {
			nationalitiesMap[p.Nationality] = true
		}
		if p.Gender != "" {
			gendersMap[p.Gender] = true
		}
	}

	var nationalities []string
	for nat := range nationalitiesMap {
		nationalities = append(nationalities, nat)
	}

	var genders []string
	for gender := range gendersMap {
		genders = append(genders, gender)
	}

	data := map[string]interface{}{
		"PageTitle":           "Performers",
		"ActivePage":          "performers",
		"Performers":          performersWithCounts,
		"Query":               query,
		"Nationalities":       nationalities,
		"Genders":             genders,
		"SelectedNationality": nationalityFilter,
		"SelectedGender":      genderFilter,
	}

	s.templates.ExecuteTemplate(w, "performers.html", data)
}

// calculateAge calculates age from birthday string (YYYY-MM-DD)
func calculateAge(birthday string) int {
	if birthday == "" {
		return 0
	}

	birthDate, err := time.Parse("2006-01-02", birthday)
	if err != nil {
		return 0
	}

	now := time.Now()
	age := now.Year() - birthDate.Year()

	// Adjust if birthday hasn't occurred yet this year
	if now.Month() < birthDate.Month() ||
		(now.Month() == birthDate.Month() && now.Day() < birthDate.Day()) {
		age--
	}

	return age
}

// getCountryFlag converts ISO country code to flag emoji
func getCountryFlag(countryCode string) string {
	if countryCode == "" {
		return ""
	}

	// Map of common ISO country codes to flag emojis
	// Unicode flags are formed by regional indicator symbols
	countryFlags := map[string]string{
		"US": "🇺🇸", "GB": "🇬🇧", "CA": "🇨🇦", "AU": "🇦🇺",
		"FR": "🇫🇷", "DE": "🇩🇪", "IT": "🇮🇹", "ES": "🇪🇸",
		"RU": "🇷🇺", "JP": "🇯🇵", "CN": "🇨🇳", "BR": "🇧🇷",
		"MX": "🇲🇽", "AR": "🇦🇷", "CL": "🇨🇱", "CO": "🇨🇴",
		"CZ": "🇨🇿", "HU": "🇭🇺", "PL": "🇵🇱", "RO": "🇷🇴",
		"SK": "🇸🇰", "UA": "🇺🇦", "SE": "🇸🇪", "NO": "🇳🇴",
		"FI": "🇫🇮", "DK": "🇩🇰", "NL": "🇳🇱", "BE": "🇧🇪",
		"CH": "🇨🇭", "AT": "🇦🇹", "PT": "🇵🇹", "GR": "🇬🇷",
		"IE": "🇮🇪", "NZ": "🇳🇿", "ZA": "🇿🇦", "IN": "🇮🇳",
		"TH": "🇹🇭", "VN": "🇻🇳", "PH": "🇵🇭", "ID": "🇮🇩",
		"MY": "🇲🇾", "SG": "🇸🇬", "KR": "🇰🇷", "TW": "🇹🇼",
		"HK": "🇭🇰", "TR": "🇹🇷", "IL": "🇮🇱", "EG": "🇪🇬",
	}

	if flag, ok := countryFlags[countryCode]; ok {
		return flag
	}

	// For codes not in our map, try to convert programmatically
	// This works for any valid ISO 3166-1 alpha-2 code
	if len(countryCode) == 2 {
		code := []rune(countryCode)
		if code[0] >= 'A' && code[0] <= 'Z' && code[1] >= 'A' && code[1] <= 'Z' {
			// Regional indicator symbol base
			const regionalBase = 0x1F1E6 - 'A'
			return string([]rune{
				rune(regionalBase + code[0]),
				rune(regionalBase + code[1]),
			})
		}
	}

	return "🌍" // Default globe emoji
}

func (s *Server) handlePerformerDetail(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Path[len("/performers/"):]
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		http.Error(w, "Invalid performer ID", http.StatusBadRequest)
		return
	}

	performerStore := db.NewPerformerStore(s.db)
	sceneStore := db.NewSceneStore(s.db)

	performer, err := performerStore.GetByID(id)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	sceneCount, _ := performerStore.GetSceneCount(id)
	scenes, _ := sceneStore.GetByPerformer(id)

	data := map[string]interface{}{
		"PageTitle":   fmt.Sprintf("Performer: %s", performer.Name),
		"ActivePage":  "performers",
		"Stylesheets": []string{"/static/css/style.css", "/static/css/goondex.css"},
		"Performer":   performer,
		"SceneCount":  sceneCount,
		"Scenes":      scenes,
	}

	s.templates.ExecuteTemplate(w, "performer_detail.html", data)
}

func (s *Server) handleStudioList(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")

	store := db.NewStudioStore(s.db)
	studios, err := store.Search(query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	type StudioWithCount struct {
		Studio     interface{}
		SceneCount int
	}

	var studiosWithCounts []StudioWithCount
	for _, st := range studios {
		count, _ := store.GetSceneCount(st.ID)
		studiosWithCounts = append(studiosWithCounts,
			StudioWithCount{Studio: st, SceneCount: count})
	}

	data := map[string]interface{}{
		"PageTitle":  "Studios",
		"ActivePage": "studios",
		"Studios":    studiosWithCounts,
		"Query":      query,
	}

	s.templates.ExecuteTemplate(w, "studios.html", data)
}

func (s *Server) handleStudioDetail(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Path[len("/studios/"):]
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		http.Error(w, "Invalid studio ID", http.StatusBadRequest)
		return
	}

	store := db.NewStudioStore(s.db)
	studio, err := store.GetByID(id)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	sceneCount, _ := store.GetSceneCount(id)

	data := map[string]interface{}{
		"PageTitle":   fmt.Sprintf("Studio: %s", studio.Name),
		"ActivePage":  "studios",
		"Stylesheets": []string{"/static/css/style.css", "/static/css/goondex.css"},
		"Studio":      studio,
		"SceneCount":  sceneCount,
	}

	s.templates.ExecuteTemplate(w, "studio_detail.html", data)
}

func (s *Server) handleSceneList(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")

	sceneStore := db.NewSceneStore(s.db)
	studioStore := db.NewStudioStore(s.db)

	scenes, err := sceneStore.Search(query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	type SceneWithStudio struct {
		Scene      interface{}
		StudioName string
	}

	var scenesWithStudios []SceneWithStudio
	for _, sc := range scenes {
		studioName := ""

		if sc.StudioID != nil && *sc.StudioID > 0 {
			if studio, err := studioStore.GetByID(*sc.StudioID); err == nil {
				studioName = studio.Name
			}
		}

		scenesWithStudios = append(scenesWithStudios,
			SceneWithStudio{Scene: sc, StudioName: studioName})
	}

	data := map[string]interface{}{
		"PageTitle":  "Scenes",
		"ActivePage": "scenes",
		"Scenes":     scenesWithStudios,
		"Query":      query,
	}

	s.templates.ExecuteTemplate(w, "scenes.html", data)
}

func (s *Server) handleSceneDetail(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Path[len("/scenes/"):]
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		http.Error(w, "Invalid scene ID", http.StatusBadRequest)
		return
	}

	sceneStore := db.NewSceneStore(s.db)
	studioStore := db.NewStudioStore(s.db)

	scene, err := sceneStore.GetByID(id)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	performers, _ := sceneStore.GetPerformers(id)
	tags, _ := sceneStore.GetTags(id)
	movies, _ := sceneStore.GetMovies(id)

	studioName := ""
	if scene.StudioID != nil && *scene.StudioID > 0 {
		if studio, err := studioStore.GetByID(*scene.StudioID); err == nil {
			studioName = studio.Name
		}
	}

	data := map[string]interface{}{
		"PageTitle":   fmt.Sprintf("Scene: %s", scene.Title),
		"ActivePage":  "scenes",
		"Stylesheets": []string{"/static/css/style.css", "/static/css/goondex.css"},
		"Scene":       scene,
		"Performers":  performers,
		"Tags":        tags,
		"Movies":      movies,
		"StudioName":  studioName,
	}

	s.templates.ExecuteTemplate(w, "scene_detail.html", data)
}

func (s *Server) handleMovieList(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")

	movieStore := db.NewMovieStore(s.db)
	studioStore := db.NewStudioStore(s.db)

	movies, err := movieStore.Search(query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	type MovieWithDetails struct {
		Movie      interface{}
		StudioName string
		SceneCount int
	}

	var moviesWithDetails []MovieWithDetails
	for _, m := range movies {
		studioName := ""
		if m.StudioID != nil && *m.StudioID > 0 {
			if studio, err := studioStore.GetByID(*m.StudioID); err == nil {
				studioName = studio.Name
			}
		}

		sceneCount, _ := movieStore.GetSceneCount(m.ID)

		moviesWithDetails = append(moviesWithDetails,
			MovieWithDetails{Movie: m, StudioName: studioName, SceneCount: sceneCount})
	}

	data := map[string]interface{}{
		"PageTitle":  "Movies",
		"ActivePage": "movies",
		"Movies":     moviesWithDetails,
		"Query":      query,
	}

	s.templates.ExecuteTemplate(w, "movies.html", data)
}

func (s *Server) handleMovieDetail(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Path[len("/movies/"):]
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		http.Error(w, "Invalid movie ID", http.StatusBadRequest)
		return
	}

	movieStore := db.NewMovieStore(s.db)
	studioStore := db.NewStudioStore(s.db)

	movie, err := movieStore.GetByID(id)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	scenes, _ := movieStore.GetScenes(id)

	studioName := ""
	if movie.StudioID != nil && *movie.StudioID > 0 {
		if studio, err := studioStore.GetByID(*movie.StudioID); err == nil {
			studioName = studio.Name
		}
	}

	data := map[string]interface{}{
		"PageTitle":  fmt.Sprintf("Movie: %s", movie.Title),
		"ActivePage": "movies",
		"Movie":      movie,
		"Scenes":     scenes,
		"StudioName": studioName,
	}

	s.templates.ExecuteTemplate(w, "movie_detail.html", data)
}

// ============================================================================
// API HANDLERS (NO LOGIC CHANGES — ONLY STATIC FIXES ABOVE)
// ============================================================================

type APIResponse struct {
	Success bool        `json:"success"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

const tpdbEnabled = true
const tpdbDisabledMessage = "TPDB integration is disabled. Use the Adult Empire CLI commands to import and enrich data for now."

func tpdbAPIKey() (string, error) {
	if !tpdbEnabled {
		return "", fmt.Errorf(tpdbDisabledMessage)
	}

	apiKey := config.GetAPIKeys().TPDBAPIKey
	if apiKey == "" {
		return "", fmt.Errorf("TPDB_API_KEY not configured")
	}

	return apiKey, nil
}

func writeTPDBError(w http.ResponseWriter, err error) bool {
	if err == nil {
		return false
	}
	json.NewEncoder(w).Encode(APIResponse{Success: false, Message: err.Error()})
	return true
}

func (s *Server) handleAPIImportPerformer(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Query string `json:"query"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "Invalid request"})
		return
	}

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	performers, err := scraper.SearchPerformers(context.Background(), req.Query)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Search failed: %v", err)})
		return
	}

	if len(performers) == 0 {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "No performers found"})
		return
	}

	store := db.NewPerformerStore(s.db)
	imported := 0

	for _, p := range performers {
		fullPerformer, err := scraper.GetPerformerByID(context.Background(), p.SourceID)
		if err != nil {
			fullPerformer = &p
		}

		if err := store.Create(fullPerformer); err != nil {
			continue
		}
		imported++
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %d performer(s)", imported),
		Data:    map[string]int{"imported": imported, "found": len(performers)},
	})
}

func (s *Server) handleAPIImportStudio(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Query string `json:"query"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "Invalid request"})
		return
	}

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	studios, err := scraper.SearchStudios(context.Background(), req.Query)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Search failed: %v", err)})
		return
	}

	if len(studios) == 0 {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "No studios found"})
		return
	}

	store := db.NewStudioStore(s.db)
	imported := 0

	for _, st := range studios {
		if err := store.Create(&st); err != nil {
			continue
		}
		imported++
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %d studio(s)", imported),
		Data:    map[string]int{"imported": imported, "found": len(studios)},
	})
}

func (s *Server) handleAPIImportScene(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Query string `json:"query"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "Invalid request"})
		return
	}

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	scenes, err := scraper.SearchScenes(context.Background(), req.Query)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Search failed: %v", err)})
		return
	}

	if len(scenes) == 0 {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "No scenes found"})
		return
	}

	sceneStore := db.NewSceneStore(s.db)
	performerStore := db.NewPerformerStore(s.db)
	studioStore := db.NewStudioStore(s.db)
	tagStore := db.NewTagStore(s.db)

	imported := 0

	for _, sc := range scenes {

		if sc.Studio != nil {
			if err := studioStore.Create(sc.Studio); err != nil {
				studios, _ := studioStore.Search(sc.Studio.Name)
				if len(studios) > 0 {
					sc.StudioID = &studios[0].ID
				}
			} else {
				sc.StudioID = &sc.Studio.ID
			}
		}

		if err := sceneStore.Create(&sc); err != nil {
			continue
		}

		for _, p := range sc.Performers {
			if err := performerStore.Create(&p); err != nil {
				performers, _ := performerStore.Search(p.Name)
				if len(performers) > 0 {
					p.ID = performers[0].ID
				}
			}
			if p.ID > 0 {
				sceneStore.AddPerformer(sc.ID, p.ID)
			}
		}

		for _, t := range sc.Tags {
			existing, _ := tagStore.GetByName(t.Name)
			if existing != nil {
				t.ID = existing.ID
			} else {
				if err := tagStore.Create(&t); err != nil {
					continue
				}
			}
			if t.ID > 0 {
				sceneStore.AddTag(sc.ID, t.ID)
			}
		}

		imported++
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %d scene(s)", imported),
		Data:    map[string]int{"imported": imported, "found": len(scenes)},
	})
}

func (s *Server) handleAPISync(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Force bool `json:"force"`
	}
	json.NewDecoder(r.Body).Decode(&req)

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := sync.NewService(s.db, scraper)

	opts := sync.SyncOptions{
		Force:       req.Force,
		MinInterval: 24 * time.Hour,
	}

	results, err := service.SyncAll(context.Background(), opts)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Sync failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: "Sync completed",
		Data:    results,
	})
}

func (s *Server) handleAPISyncStatus(w http.ResponseWriter, r *http.Request) {
	if writeTPDBError(w, fmt.Errorf(tpdbDisabledMessage)) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", "")
	service := sync.NewService(s.db, scraper)

	statuses, err := service.GetSyncStatus()
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Failed to get status: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: "Status retrieved",
		Data:    statuses,
	})
}

// ============================================================================
// Adult Empire API (search + scrape)
// ============================================================================

func (s *Server) handleAEImportPerformer(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Query string `json:"query"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.Query) == "" {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "query is required"})
		return
	}

	scraper, err := adultemp.NewScraper()
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scraper init failed: %v", err)})
		return
	}

	results, err := scraper.SearchPerformersByName(r.Context(), req.Query)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("search failed: %v", err)})
		return
	}

	if len(results) == 0 {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "No performers found on Adult Empire"})
		return
	}

	top := results[0]
	data, err := scraper.ScrapePerformerByURL(r.Context(), top.URL)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scrape failed: %v", err)})
		return
	}

	performer := scraper.ConvertPerformerToModel(data)

	store := db.NewPerformerStore(s.db)
	if err := store.Create(performer); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("save failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %s from Adult Empire", performer.Name),
		Data: map[string]interface{}{
			"name": performer.Name,
			"url":  top.URL,
		},
	})
}

func (s *Server) handleAEImportPerformerByURL(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		URL string `json:"url"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.URL) == "" {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "url is required"})
		return
	}

	scraper, err := adultemp.NewScraper()
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scraper init failed: %v", err)})
		return
	}

	data, err := scraper.ScrapePerformerByURL(r.Context(), req.URL)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scrape failed: %v", err)})
		return
	}

	performer := scraper.ConvertPerformerToModel(data)
	store := db.NewPerformerStore(s.db)
	if err := store.Create(performer); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("save failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %s from Adult Empire", performer.Name),
		Data: map[string]interface{}{
			"name": performer.Name,
			"url":  req.URL,
		},
	})
}

func (s *Server) handleAEImportScene(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Query string `json:"query"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.Query) == "" {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "query is required"})
		return
	}

	scraper, err := adultemp.NewScraper()
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scraper init failed: %v", err)})
		return
	}

	results, err := scraper.SearchScenesByName(r.Context(), req.Query)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("search failed: %v", err)})
		return
	}

	if len(results) == 0 {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "No scenes found on Adult Empire"})
		return
	}

	top := results[0]
	data, err := scraper.ScrapeSceneByURL(r.Context(), top.URL)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scrape failed: %v", err)})
		return
	}

	scene := scraper.ConvertSceneToModel(data)
	sceneStore := db.NewSceneStore(s.db)

	if err := sceneStore.Create(scene); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("save failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported scene: %s", scene.Title),
		Data: map[string]interface{}{
			"title": scene.Title,
			"url":   top.URL,
		},
	})
}

func (s *Server) handleAEImportSceneByURL(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		URL string `json:"url"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.URL) == "" {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "url is required"})
		return
	}

	scraper, err := adultemp.NewScraper()
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scraper init failed: %v", err)})
		return
	}

	data, err := scraper.ScrapeSceneByURL(r.Context(), req.URL)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("scrape failed: %v", err)})
		return
	}

	scene := scraper.ConvertSceneToModel(data)
	sceneStore := db.NewSceneStore(s.db)

	if err := sceneStore.Create(scene); err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("save failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported scene: %s", scene.Title),
		Data: map[string]interface{}{
			"title": scene.Title,
			"url":   req.URL,
		},
	})
}

// ============================================================================
// BULK IMPORT ENDPOINTS + SSE PROGRESS
// ============================================================================

func (s *Server) handleAPIBulkImportAll(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := import_service.NewService(s.db, scraper)

	results, err := service.BulkImportAll(context.Background())
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Bulk import failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: "Bulk import completed successfully",
		Data:    results,
	})
}

func (s *Server) handleAPIBulkImportPerformers(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	// Try Adult Empire first (primary scraper for new imports)
	bulkScraper, err := scraper.NewAdultEmpireBulkScraper()
	if err != nil {
		// Fall back to TPDB if Adult Empire fails
		apiKey, keyErr := tpdbAPIKey()
		if writeTPDBError(w, keyErr) {
			return
		}

		tpdbScraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
		service := import_service.NewService(s.db, tpdbScraper)
		if enricher, enrichErr := import_service.NewEnricher(s.db, 1*time.Second); enrichErr == nil {
			service.WithEnricher(enricher)
		}

		result, err := service.BulkImportAllPerformers(context.Background())
		s.writeImportResult(w, result, err, "Performers")
		return
	}

	// Use Adult Empire scraper
	service := import_service.NewFlexibleService(s.db, bulkScraper)
	result, err := service.BulkImportAllPerformersFlexible(context.Background())
	s.writeImportResult(w, result, err, "Performers")
}

func (s *Server) handleAPIBulkImportStudios(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := import_service.NewService(s.db, scraper)

	result, err := service.BulkImportAllStudios(context.Background())
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Import failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %d/%d studios", result.Imported, result.Total),
		Data:    result,
	})
}

func (s *Server) handleAPIBulkImportScenes(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	apiKey, err := tpdbAPIKey()
	if writeTPDBError(w, err) {
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := import_service.NewService(s.db, scraper)

	result, err := service.BulkImportAllScenes(context.Background())
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Import failed: %v", err)})
		return
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Imported %d/%d scenes", result.Imported, result.Total),
		Data:    result,
	})
}

func (s *Server) handleAPIBulkImportPerformersProgress(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Access-Control-Allow-Origin", "*")

	apiKey, err := tpdbAPIKey()
	if err != nil {
		fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error())
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := import_service.NewService(s.db, scraper)

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	progressCallback := func(update import_service.ProgressUpdate) {
		data, _ := json.Marshal(update)
		fmt.Fprintf(w, "data: %s\n\n", data)
		flusher.Flush()
	}

	result, err := service.BulkImportAllPerformersWithProgress(
		context.Background(), progressCallback)

	if err != nil {
		fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error())
	} else {
		data, _ := json.Marshal(map[string]interface{}{
			"complete": true,
			"result":   result,
		})
		fmt.Fprintf(w, "data: %s\n\n", data)
	}

	flusher.Flush()
}

func (s *Server) handleAPIBulkImportStudiosProgress(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Access-Control-Allow-Origin", "*")

	apiKey, err := tpdbAPIKey()
	if err != nil {
		fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error())
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := import_service.NewService(s.db, scraper)

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	progressCallback := func(update import_service.ProgressUpdate) {
		data, _ := json.Marshal(update)
		fmt.Fprintf(w, "data: %s\n\n", data)
		flusher.Flush()
	}

	result, err := service.BulkImportAllStudiosWithProgress(
		context.Background(), progressCallback)

	if err != nil {
		fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error())
	} else {
		data, _ := json.Marshal(map[string]interface{}{
			"complete": true,
			"result":   result,
		})
		fmt.Fprintf(w, "data: %s\n\n", data)
	}

	flusher.Flush()
}

func (s *Server) handleAPIBulkImportScenesProgress(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Access-Control-Allow-Origin", "*")

	apiKey, err := tpdbAPIKey()
	if err != nil {
		fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error())
		return
	}

	scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey)
	service := import_service.NewService(s.db, scraper)

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	progressCallback := func(update import_service.ProgressUpdate) {
		data, _ := json.Marshal(update)
		fmt.Fprintf(w, "data: %s\n\n", data)
		flusher.Flush()
	}

	result, err := service.BulkImportAllScenesWithProgress(
		context.Background(), progressCallback)

	if err != nil {
		fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error())
	} else {
		data, _ := json.Marshal(map[string]interface{}{
			"complete": true,
			"result":   result,
		})
		fmt.Fprintf(w, "data: %s\n\n", data)
	}

	flusher.Flush()
}

// ============================================================================
// GLOBAL SEARCH
// ============================================================================

func (s *Server) handleAPIGlobalSearch(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	query := r.URL.Query().Get("q")
	if query == "" {
		json.NewEncoder(w).Encode(APIResponse{
			Success: false,
			Message: "Query parameter required",
		})
		return
	}

	// Use advanced search for complex queries
	advancedSearch := search.NewAdvancedSearch(s.db)
	results, err := advancedSearch.Search(query, 20)
	if err != nil {
		json.NewEncoder(w).Encode(APIResponse{
			Success: false,
			Message: fmt.Sprintf("Search failed: %v", err),
		})
		return
	}

	// Convert to format expected by frontend
	scenes := make([]model.Scene, len(results))
	for i, result := range results {
		scenes[i] = result.Scene
	}

	response := map[string]interface{}{
		"scenes":       scenes,
		"total":        len(results),
		"advanced":     true,
		"search_query": query,
	}

	json.NewEncoder(w).Encode(APIResponse{
		Success: true,
		Message: fmt.Sprintf("Found %d advanced results", len(results)),
		Data:    response,
	})

}

// ============================================================================
// SETTINGS
// ============================================================================

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

	data := map[string]interface{}{
		"PageTitle":  "Settings",
		"ActivePage": "settings",
		"DBPath":     s.dbPath,
	}

	s.render(w, "settings.html", data)
}

type apiKeysPayload struct {
	TPDBAPIKey      string `json:"tpdb_api_key"`
	AEAPIKey        string `json:"ae_api_key"`
	StashDBAPIKey   string `json:"stashdb_api_key"`
	StashDBEndpoint string `json:"stashdb_endpoint"`
}

func (s *Server) handleAPISettingsKeys(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		keys := config.GetAPIKeys()
		resp := map[string]interface{}{
			"tpdbConfigured":    keys.TPDBAPIKey != "",
			"aeConfigured":      keys.AEAPIKey != "",
			"stashdbConfigured": keys.StashDBAPIKey != "",
			"stashdbEndpoint":   keys.StashDBEndpoint,
			"tpdb_api_key":      keys.TPDBAPIKey, // local-only UI; if you prefer, mask these
			"ae_api_key":        keys.AEAPIKey,
			"stashdb_api_key":   keys.StashDBAPIKey,
			"stashdb_endpoint":  keys.StashDBEndpoint, // duplicate for UI convenience
		}
		json.NewEncoder(w).Encode(APIResponse{
			Success: true,
			Message: "OK",
			Data:    resp,
		})
	case http.MethodPost:
		var payload apiKeysPayload
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "Invalid JSON"})
			return
		}

		keys := config.APIKeys{
			TPDBAPIKey:      payload.TPDBAPIKey,
			AEAPIKey:        payload.AEAPIKey,
			StashDBAPIKey:   payload.StashDBAPIKey,
			StashDBEndpoint: payload.StashDBEndpoint,
		}

		if err := config.SaveAPIKeys(keys); err != nil {
			json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Failed to save keys: %v", err)})
			return
		}

		json.NewEncoder(w).Encode(APIResponse{
			Success: true,
			Message: "API keys saved",
		})
	default:
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

// Database management
func (s *Server) handleAPIDatabase(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		info := map[string]interface{}{
			"path": s.dbPath,
		}
		if stat, err := os.Stat(s.dbPath); err == nil {
			info["size_bytes"] = stat.Size()
			info["size_mb"] = float64(stat.Size()) / (1024 * 1024)
		}
		json.NewEncoder(w).Encode(APIResponse{
			Success: true,
			Message: "OK",
			Data:    info,
		})
	case http.MethodDelete:
		// Close and recreate
		if s.db != nil {
			_ = s.db.Close()
		}
		_ = os.Remove(s.dbPath)
		newDB, err := db.Open(s.dbPath)
		if err != nil {
			json.NewEncoder(w).Encode(APIResponse{Success: false, Message: fmt.Sprintf("Failed to recreate DB: %v", err)})
			return
		}
		s.db = newDB
		json.NewEncoder(w).Encode(APIResponse{
			Success: true,
			Message: "Database deleted and recreated.",
		})
	default:
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}
