package web

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"starchive/podpapyrus"
	"starchive/util"
)

var (
	storedPOToken string
	poTokenMutex  sync.RWMutex
	poTokenTime   time.Time
)


// WriteCookiesFile creates a Netscape format cookies file for a specific platform
func WriteCookiesFile(cookiesData interface{}, platform string) error {
	filename := fmt.Sprintf("./cookies_%s.txt", platform)
	file, err := os.Create(filename)
	if err != nil {
		return fmt.Errorf("failed to create cookies file: %v", err)
	}
	defer file.Close()

	// Write Netscape HTTP Cookie File header
	file.WriteString("# Netscape HTTP Cookie File\n")
	file.WriteString("# This file is generated by yt-dlp.  Do not edit.\n\n")

	// Handle different input formats
	switch data := cookiesData.(type) {
	case string:
		// Legacy string format - simple cookie string
		return writeCookiesFromString(file, data, platform)
	case []interface{}:
		// Array format from Firefox extension
		return writeCookiesFromArray(file, data)
	default:
		return fmt.Errorf("unsupported cookies data format")
	}
}

// writeCookiesFromArray processes cookie objects from Firefox extension
func writeCookiesFromArray(file *os.File, cookiesArray []interface{}) error {
	fmt.Printf("Processing %d cookies from extension\n", len(cookiesArray))

	for i, cookieData := range cookiesArray {
		cookie, ok := cookieData.(map[string]interface{})
		if !ok {
			fmt.Printf("Skipping invalid cookie at index %d\n", i)
			continue
		}

		name, nameOk := cookie["name"].(string)
		value, valueOk := cookie["value"].(string)
		domain, domainOk := cookie["domain"].(string)
		path, pathOk := cookie["path"].(string)

		if !nameOk || !valueOk || !domainOk || name == "" || value == "" || domain == "" {
			fmt.Printf("Skipping incomplete cookie: name=%v, value=%v, domain=%v\n", nameOk, valueOk, domainOk)
			continue
		}

		if !pathOk || path == "" {
			path = "/"
		}

		// Extract other attributes with defaults
		secure := false
		if s, ok := cookie["secure"].(bool); ok {
			secure = s
		}

		expires := int64(0)
		if exp, ok := cookie["expires"].(float64); ok && exp > 0 {
			expires = int64(exp)
		}

		// Determine if domain should be treated as subdomain
		domainSpecified := "TRUE"
		if !strings.HasPrefix(domain, ".") {
			domainSpecified = "FALSE"
		}

		// Write in Netscape format: domain, domain_specified, path, secure, expiration, name, value
		line := fmt.Sprintf("%s\t%s\t%s\t%s\t%d\t%s\t%s\n",
			domain,
			domainSpecified,
			path,
			strings.ToUpper(fmt.Sprintf("%t", secure)),
			expires,
			name,
			value)

		if _, err := file.WriteString(line); err != nil {
			return fmt.Errorf("failed to write cookie %s: %v", name, err)
		}
	}

	fmt.Printf("Successfully wrote cookies to file\n")
	return nil
}

// writeCookiesFromString handles legacy string format
func writeCookiesFromString(file *os.File, cookiesStr, platform string) error {
	// Parse and write cookies in legacy format
	cookies := strings.Split(cookiesStr, "; ")
	domain := fmt.Sprintf(".%s.com", platform)

	for _, cookie := range cookies {
		parts := strings.SplitN(cookie, "=", 2)
		if len(parts) == 2 {
			// Format: domain, domain_specified, path, secure, expiration, name, value
			line := fmt.Sprintf("%s\tTRUE\t/\tFALSE\t0\t%s\t%s\n", domain, parts[0], parts[1])
			file.WriteString(line)
		}
	}

	return nil
}

// SetupRoutes configures HTTP routes for the web server
func SetupRoutes(downloadQueue interface{}) {
	http.HandleFunc("/api/download", func(w http.ResponseWriter, r *http.Request) {
		handleDownload(w, r, downloadQueue)
	})
	http.HandleFunc("/api/cookies", handleSetCookies)
	http.HandleFunc("/youtube", func(w http.ResponseWriter, r *http.Request) {
		handleYouTube(w, r, downloadQueue)
	})
	http.HandleFunc("/instagram", func(w http.ResponseWriter, r *http.Request) {
		handleInstagram(w, r, downloadQueue)
	})
	http.HandleFunc("/get-txt", func(w http.ResponseWriter, r *http.Request) {
		handleGetTxt(w, r, downloadQueue)
	})
	http.HandleFunc("/po-token", handlePOToken)
	http.HandleFunc("/data", handleData)
	http.HandleFunc("/", handleStatic)
}

func handleDownload(w http.ResponseWriter, r *http.Request, downloadQueue interface{}) {
	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 {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	if req.URL == "" {
		http.Error(w, "URL is required", http.StatusBadRequest)
		return
	}

	// Add to download queue (implementation depends on queue structure)
	fmt.Printf("Download requested: %s\n", req.URL)

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"status": "queued",
		"url":    req.URL,
	})
}

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

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading request body", http.StatusInternalServerError)
		return
	}

	var req struct {
		Cookies string `json:"cookies"`
	}

	if err := json.Unmarshal(body, &req); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	if req.Cookies == "" {
		http.Error(w, "Cookies are required", http.StatusBadRequest)
		return
	}

	if err := WriteCookiesFile(req.Cookies, "youtube"); err != nil {
		http.Error(w, fmt.Sprintf("Error writing cookies file: %v", err), http.StatusInternalServerError)
		return
	}

	fmt.Println("Cookies file updated successfully")

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"status":  "success",
		"message": "Cookies updated successfully",
	})
}

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

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	var jsonData map[string]interface{}
	if err := json.Unmarshal(body, &jsonData); err != nil {
		fmt.Printf("Invalid JSON received: %s\n", string(body))
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	fmt.Printf("JSON received: %+v\n", jsonData)

	id, ok := jsonData["videoId"].(string)
	if !ok {
		http.Error(w, "Missing or invalid 'videoId' field", http.StatusBadRequest)
		return
	}

	// Handle cookies if provided - support both string and array formats
	if cookies, ok := jsonData["cookies"].(string); ok && cookies != "" {
		fmt.Printf("Processing string format cookies (%d chars)\n", len(cookies))
		if err := WriteCookiesFile(cookies, "youtube"); err != nil {
			fmt.Printf("Warning: failed to write YouTube cookies: %v\n", err)
		}
	} else if cookiesArray, ok := jsonData["cookies"].([]interface{}); ok && len(cookiesArray) > 0 {
		fmt.Printf("Processing array format cookies (%d items)\n", len(cookiesArray))
		// Pass the cookie array directly to WriteCookiesFile
		if err := WriteCookiesFile(cookiesArray, "youtube"); err != nil {
			fmt.Printf("Warning: failed to write YouTube cookies: %v\n", err)
		}
	} else {
		fmt.Printf("No cookies provided or invalid cookie format\n")
	}

	// Add to download queue
	if queue, ok := downloadQueue.(*DownloadQueue); ok {
		added := queue.AddToQueue(id)
		if !added {
			fmt.Fprintf(w, "Video %s is already in download queue", id)
			return
		}

		queueLength, isRunning := queue.GetQueueStatus()
		fmt.Fprintf(w, "Video %s added to download queue. Queue length: %d, Processing: %t", id, queueLength, isRunning)
	} else {
		http.Error(w, "Download queue not available", http.StatusInternalServerError)
	}
}

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

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	var jsonData map[string]interface{}
	if err := json.Unmarshal(body, &jsonData); err != nil {
		fmt.Printf("Invalid JSON received: %s\n", string(body))
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	fmt.Printf("Instagram JSON received: %+v\n", jsonData)

	id, ok := jsonData["postId"].(string)
	if !ok {
		http.Error(w, "Missing or invalid 'postId' field", http.StatusBadRequest)
		return
	}

	// Handle cookies if provided - support both string and array formats
	if cookies, ok := jsonData["cookies"].(string); ok && cookies != "" {
		if err := WriteCookiesFile(cookies, "instagram"); err != nil {
			fmt.Printf("Warning: failed to write Instagram cookies: %v\n", err)
		}
	} else if cookiesArray, ok := jsonData["cookies"].([]interface{}); ok && len(cookiesArray) > 0 {
		// Convert array format to string format
		cookiesStr := ""
		for _, cookie := range cookiesArray {
			if c, ok := cookie.(map[string]interface{}); ok {
				name, nameOk := c["name"].(string)
				value, valueOk := c["value"].(string)
				if nameOk && valueOk && name != "" && value != "" {
					if cookiesStr != "" {
						cookiesStr += "; "
					}
					cookiesStr += name + "=" + value
				}
			}
		}
		if cookiesStr != "" {
			if err := WriteCookiesFile(cookiesStr, "instagram"); err != nil {
				fmt.Printf("Warning: failed to write Instagram cookies: %v\n", err)
			}
		}
	}

	// Add to download queue
	if queue, ok := downloadQueue.(*DownloadQueue); ok {
		added := queue.AddToQueue(id)
		if !added {
			fmt.Fprintf(w, "Instagram post %s is already in download queue", id)
			return
		}

		queueLength, isRunning := queue.GetQueueStatus()
		fmt.Fprintf(w, "Instagram post %s added to download queue. Queue length: %d, Processing: %t", id, queueLength, isRunning)
	} else {
		http.Error(w, "Download queue not available", http.StatusInternalServerError)
	}
}

func handleData(w http.ResponseWriter, r *http.Request) {
	fmt.Printf("[Starchive] GET /data request received\n")

	if r.Method != http.MethodGet {
		fmt.Printf("[Starchive] Wrong method for /data: %s\n", r.Method)
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Get disk usage for ./data directory
	fmt.Printf("[Starchive] Getting disk usage for ./data directory\n")
	total, used, free, err := util.Usage("./data")
	var diskInfo map[string]interface{}
	if err != nil {
		fmt.Printf("[Starchive] Error getting disk usage: %v\n", err)
		diskInfo = map[string]interface{}{
			"error": fmt.Sprintf("Unable to get disk usage: %v", err),
		}
	} else {
		fmt.Printf("[Starchive] Disk usage - Total: %d bytes (%s), Used: %d bytes (%s), Free: %d bytes (%s)\n",
			total, util.Pretty(total), used, util.Pretty(used), free, util.Pretty(free))

		// Get actual size of ./data directory
		dataSize, dataSizeErr := util.DirSize("./data")
		var dataSizePretty string
		var dataPercentOfFree float64

		if dataSizeErr != nil {
			fmt.Printf("[Starchive] Error getting data directory size: %v\n", dataSizeErr)
			dataSizePretty = "Error"
			dataPercentOfFree = 0
		} else {
			dataSizePretty = util.Pretty(uint64(dataSize))
			dataPercentOfFree = (float64(dataSize) / float64(free)) * 100
			fmt.Printf("[Starchive] Data directory size: %d bytes (%s), %.1f%% of free space\n",
				dataSize, dataSizePretty, dataPercentOfFree)
		}

		diskInfo = map[string]interface{}{
			"total":             total,
			"used":              used,
			"free":              free,
			"totalPretty":       util.Pretty(total),
			"usedPretty":        util.Pretty(used),
			"freePretty":        util.Pretty(free),
			"dataSize":          dataSize,
			"dataSizePretty":    dataSizePretty,
			"dataPercentOfFree": dataPercentOfFree,
		}
		fmt.Printf("[Starchive] Created diskInfo object: %+v\n", diskInfo)
	}

	w.Header().Set("Content-Type", "application/json")
	response := map[string]interface{}{
		"status":    "ok",
		"message":   "Starchive server is running",
		"diskUsage": diskInfo,
	}

	fmt.Printf("[Starchive] Final response object: %+v\n", response)

	if err := json.NewEncoder(w).Encode(response); err != nil {
		fmt.Printf("[Starchive] Error encoding /data response: %v\n", err)
		http.Error(w, "Error encoding response", http.StatusInternalServerError)
	} else {
		fmt.Printf("[Starchive] /data response sent successfully\n")
	}
}

func handleStatic(w http.ResponseWriter, r *http.Request) {
	// Simple static file serving - in production, use proper static file server
	if r.URL.Path == "/" {
		w.Header().Set("Content-Type", "text/html")
		w.Write([]byte(`
<!DOCTYPE html>
<html>
<head>
    <title>Starchive</title>
</head>
<body>
    <h1>Starchive Download Server</h1>
    <p>Use the API endpoints to download videos and set cookies.</p>
    <h2>API Endpoints:</h2>
    <ul>
        <li><strong>POST /api/download</strong> - Queue a download</li>
        <li><strong>POST /api/cookies</strong> - Set cookies</li>
    </ul>
</body>
</html>
		`))
		return
	}

	http.NotFound(w, r)
}

func handleGetTxt(w http.ResponseWriter, r *http.Request, downloadQueue interface{}) {
	fmt.Printf("[Starchive] GET /get-txt request received\n")

	if r.Method != http.MethodGet {
		fmt.Printf("[Starchive] Wrong method: %s\n", r.Method)
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	videoId := r.URL.Query().Get("id")
	mode := r.URL.Query().Get("mode")
	fmt.Printf("[Starchive] Requested video ID: %s, mode: %s\n", videoId, mode)

	if videoId == "" {
		fmt.Printf("[Starchive] No video ID provided\n")
		http.Error(w, "Video ID is required", http.StatusBadRequest)
		return
	}

	// Handle podpapyrus mode
	if mode == "podpapyrus" {
		result, err := podpapyrus.ProcessVideo(videoId, podpapyrus.BasePath)
		if err != nil {
			fmt.Printf("[Starchive] Error processing video: %v\n", err)
			http.Error(w, fmt.Sprintf("Error processing video: %v", err), http.StatusInternalServerError)
			return
		}
		
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(result)
		if result.HasContent {
			fmt.Printf("[Starchive] Served %d bytes for video %s\n", len(result.Content), videoId)
		} else {
			fmt.Printf("[Starchive] Returned message: %s for video %s\n", result.Message, videoId)
		}
		return
	}

	txtFilePath := fmt.Sprintf("./data/%s.txt", videoId)
	fmt.Printf("[Starchive] Checking for txt file at: %s\n", txtFilePath)

	if _, err := os.Stat(txtFilePath); err == nil {
		fmt.Printf("[Starchive] Txt file exists, serving content\n")
		content, err := os.ReadFile(txtFilePath)
		if err != nil {
			fmt.Printf("[Starchive] Error reading txt file: %v\n", err)
			http.Error(w, "Error reading txt file", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		response := map[string]interface{}{
			"hasContent": true,
			"content":    string(content),
			"videoId":    videoId,
		}
		json.NewEncoder(w).Encode(response)
		fmt.Printf("[Starchive] Served %d bytes for video %s\n", len(content), videoId)
		return
	}

	fmt.Printf("[Starchive] Txt file not found, attempting to queue download\n")

	if queue, ok := downloadQueue.(*DownloadQueue); ok {
		added := queue.AddToQueue(videoId)
		w.Header().Set("Content-Type", "application/json")
		if added {
			fmt.Printf("[Starchive] Added video %s to download queue\n", videoId)
			response := map[string]interface{}{
				"hasContent": false,
				"message":    "Download started",
				"videoId":    videoId,
			}
			json.NewEncoder(w).Encode(response)
		} else {
			fmt.Printf("[Starchive] Video %s already in queue\n", videoId)
			response := map[string]interface{}{
				"hasContent": false,
				"message":    "Already in download queue",
				"videoId":    videoId,
			}
			json.NewEncoder(w).Encode(response)
		}
	} else {
		fmt.Printf("[Starchive] Download queue not available\n")
		http.Error(w, "Download queue not available", http.StatusInternalServerError)
	}
}

func handlePOToken(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		// Handle GET request to retrieve current PO token
		poTokenMutex.RLock()
		token := storedPOToken
		tokenTime := poTokenTime
		poTokenMutex.RUnlock()

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

		// Return empty if token is older than 1 hour
		if time.Since(tokenTime) > time.Hour {
			json.NewEncoder(w).Encode(map[string]interface{}{
				"poToken": "",
				"message": "No valid PO token available",
			})
			return
		}

		json.NewEncoder(w).Encode(map[string]interface{}{
			"poToken":   token,
			"timestamp": tokenTime.Unix(),
		})
		return
	}

	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	var req struct {
		POToken   string `json:"poToken"`
		Timestamp int64  `json:"timestamp"`
		Source    string `json:"source"`
	}

	if err := json.Unmarshal(body, &req); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	if req.POToken == "" {
		http.Error(w, "PO token is required", http.StatusBadRequest)
		return
	}

	// Store the PO token
	poTokenMutex.Lock()
	storedPOToken = req.POToken
	poTokenTime = time.Now()
	poTokenMutex.Unlock()

	fmt.Printf("PO token received from %s: %s... (timestamp: %d)\n",
		req.Source, req.POToken[:20], req.Timestamp)

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"status":    "success",
		"message":   "PO token stored successfully",
		"timestamp": time.Now().Unix(),
	})
}

// GetStoredPOToken returns the most recently stored PO token if it's fresh (less than 1 hour old)
func GetStoredPOToken() string {
	poTokenMutex.RLock()
	defer poTokenMutex.RUnlock()

	// Return empty if token is older than 1 hour
	if time.Since(poTokenTime) > time.Hour {
		return ""
	}

	return storedPOToken
}

