package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

// Config holds runtime configuration
type Config struct {
	Port         string
	APIKey       string
	SFTPHost     string
	SFTPPort     int
	SFTPUser     string
	SFTPPassword string
	TimeoutSec   int
}

var cfg Config

// LoadEnvSimple reads a .env file if present
func loadEnvSimple(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		return
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.SplitN(line, "=", 2)
		if len(parts) == 2 {
			k := strings.TrimSpace(parts[0])
			v := strings.TrimSpace(parts[1])
			v = strings.Trim(v, `"'`)
			if os.Getenv(k) == "" {
				os.Setenv(k, v)
			}
		}
	}
}

func getEnv(key, defVal string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return defVal
}

func getEnvInt(key string, defVal int) int {
	if val := os.Getenv(key); val != "" {
		if n, err := strconv.Atoi(val); err == nil {
			return n
		}
	}
	return defVal
}

func initConfig() {
	loadEnvSimple(".env")

	cfg = Config{
		Port:         getEnv("PORT", "8099"),
		APIKey:       getEnv("API_KEY", "sftp-relay-secret-2026"),
		SFTPHost:     getEnv("SFTP_HOST", "pis-sftp.tokopedia.com"),
		SFTPPort:     getEnvInt("SFTP_PORT", 2223),
		SFTPUser:     getEnv("SFTP_USERNAME", "pps"),
		SFTPPassword: getEnv("SFTP_PASSWORD", "u+i43]97jkdf4&75f?3m"),
		TimeoutSec:   getEnvInt("SFTP_TIMEOUT", 30),
	}
}

// Format bytes into human readable string
func formatBytes(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// connectSFTP opens an SSH and SFTP connection with timeout
func connectSFTP(overrideHost string, overridePort int, overrideUser, overridePass string) (*ssh.Client, *sftp.Client, error) {
	host := cfg.SFTPHost
	port := cfg.SFTPPort
	user := cfg.SFTPUser
	pass := cfg.SFTPPassword

	if overrideHost != "" {
		host = overrideHost
	}
	if overridePort > 0 {
		port = overridePort
	}
	if overrideUser != "" {
		user = overrideUser
	}
	if overridePass != "" {
		pass = overridePass
	}

	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.Password(pass),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Bypass host key check for automated relay
		Timeout:         time.Duration(cfg.TimeoutSec) * time.Second,
	}

	addr := fmt.Sprintf("%s:%d", host, port)
	sshClient, err := ssh.Dial("tcp", addr, sshConfig)
	if err != nil {
		return nil, nil, fmt.Errorf("SSH dial error to %s: %w", addr, err)
	}

	sftpClient, err := sftp.NewClient(sshClient)
	if err != nil {
		sshClient.Close()
		return nil, nil, fmt.Errorf("SFTP client init error: %w", err)
	}

	return sshClient, sftpClient, nil
}

// JSON Response Helpers
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, message string) {
	writeJSON(w, status, map[string]interface{}{
		"success": false,
		"message": message,
	})
}

// Auth Middleware: check API Key
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// Set CORS headers
		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, X-API-Key")

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

		key := r.Header.Get("X-API-Key")
		if key == "" {
			authHeader := r.Header.Get("Authorization")
			if strings.HasPrefix(authHeader, "Bearer ") {
				key = strings.TrimPrefix(authHeader, "Bearer ")
			}
		}
		if key == "" {
			key = r.URL.Query().Get("api_key")
		}

		if cfg.APIKey != "" && key != cfg.APIKey {
			writeError(w, http.StatusUnauthorized, "Unauthorized: Invalid or missing API Key. Set header 'X-API-Key'.")
			return
		}

		next(w, r)
	}
}

// FileItem represents a remote file
type FileItem struct {
	Name      string `json:"name"`
	Path      string `json:"path"`
	IsDir     bool   `json:"is_dir"`
	Size      int64  `json:"size"`
	SizeHuman string `json:"size_human"`
	Mtime     string `json:"mtime"`
	Extension string `json:"extension"`
}

// Handler: Health Check
func handleHealth(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, map[string]interface{}{
		"service":   "Tokopedia SFTP Relay Service",
		"status":    "running",
		"version":   "1.0.0",
		"time":      time.Now().Format(time.RFC3339),
		"sftp_host": cfg.SFTPHost,
		"sftp_port": cfg.SFTPPort,
		"sftp_user": cfg.SFTPUser,
	})
}

// Handler: Test Connection
func handleTest(w http.ResponseWriter, r *http.Request) {
	overrideHost := r.URL.Query().Get("host")
	overrideUser := r.URL.Query().Get("username")
	overridePass := r.URL.Query().Get("password")
	overridePort := 0
	if p := r.URL.Query().Get("port"); p != "" {
		overridePort, _ = strconv.Atoi(p)
	}

	start := time.Now()
	sshClient, sftpClient, err := connectSFTP(overrideHost, overridePort, overrideUser, overridePass)
	if err != nil {
		writeJSON(w, http.StatusOK, map[string]interface{}{
			"success":     false,
			"message":     fmt.Sprintf("SFTP connection failed: %v", err),
			"duration_ms": time.Since(start).Milliseconds(),
			"host":        cfg.SFTPHost,
			"port":        cfg.SFTPPort,
		})
		return
	}
	defer sshClient.Close()
	defer sftpClient.Close()

	pwd, _ := sftpClient.Getwd()
	files, _ := sftpClient.ReadDir(".")

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"success":     true,
		"message":     fmt.Sprintf("Successfully connected to SFTP server (%s:%d)", cfg.SFTPHost, cfg.SFTPPort),
		"driver":      "golang_sftp_relay",
		"current_dir": pwd,
		"file_count":  len(files),
		"duration_ms": time.Since(start).Milliseconds(),
		"host":        cfg.SFTPHost,
		"port":        cfg.SFTPPort,
	})
}

// Handler: List Directory Files
func handleList(w http.ResponseWriter, r *http.Request) {
	dir := r.URL.Query().Get("path")
	if dir == "" {
		dir = "."
	}
	filterExt := strings.ToLower(r.URL.Query().Get("ext"))

	sshClient, sftpClient, err := connectSFTP("", 0, "", "")
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	defer sshClient.Close()
	defer sftpClient.Close()

	entries, err := sftpClient.ReadDir(dir)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to read directory '%s': %v", dir, err))
		return
	}

	var items []FileItem
	for _, entry := range entries {
		name := entry.Name()
		if name == "." || name == ".." {
			continue
		}

		ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(name)), ".")
		if filterExt != "" && !entry.IsDir() && ext != filterExt {
			continue
		}

		fullPath := filepath.ToSlash(filepath.Join(dir, name))
		items = append(items, FileItem{
			Name:      name,
			Path:      fullPath,
			IsDir:     entry.IsDir(),
			Size:      entry.Size(),
			SizeHuman: formatBytes(entry.Size()),
			Mtime:     entry.ModTime().Format("2006-01-02 15:04:05"),
			Extension: ext,
		})
	}

	// Sort directories first, then latest modified files
	sort.Slice(items, func(i, j int) bool {
		if items[i].IsDir != items[j].IsDir {
			return items[i].IsDir
		}
		return items[i].Mtime > items[j].Mtime
	})

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"success": true,
		"path":    dir,
		"total":   len(items),
		"files":   items,
	})
}

// Handler: Download File Stream
func handleDownload(w http.ResponseWriter, r *http.Request) {
	remotePath := r.URL.Query().Get("path")
	if remotePath == "" {
		writeError(w, http.StatusBadRequest, "Missing required parameter 'path'.")
		return
	}

	sshClient, sftpClient, err := connectSFTP("", 0, "", "")
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	defer sshClient.Close()
	defer sftpClient.Close()

	stat, err := sftpClient.Stat(remotePath)
	if err != nil {
		writeError(w, http.StatusNotFound, fmt.Sprintf("Remote file not found: %v", err))
		return
	}

	if stat.IsDir() {
		writeError(w, http.StatusBadRequest, "Target path is a directory, not a file.")
		return
	}

	remoteFile, err := sftpClient.Open(remotePath)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to open remote file: %v", err))
		return
	}
	defer remoteFile.Close()

	filename := filepath.Base(remotePath)
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
	w.Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
	w.Header().Set("X-File-Size", strconv.FormatInt(stat.Size(), 10))
	w.Header().Set("X-File-Name", filename)

	w.WriteHeader(http.StatusOK)
	_, _ = io.Copy(w, remoteFile)
}

// Handler: View / Preview File Content
func handleContent(w http.ResponseWriter, r *http.Request) {
	remotePath := r.URL.Query().Get("path")
	if remotePath == "" {
		writeError(w, http.StatusBadRequest, "Missing required parameter 'path'.")
		return
	}

	linesLimit := 100
	if l := r.URL.Query().Get("lines"); l != "" {
		if n, err := strconv.Atoi(l); err == nil && n > 0 {
			linesLimit = n
		}
	}

	sshClient, sftpClient, err := connectSFTP("", 0, "", "")
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	defer sshClient.Close()
	defer sftpClient.Close()

	stat, err := sftpClient.Stat(remotePath)
	if err != nil {
		writeError(w, http.StatusNotFound, fmt.Sprintf("Remote file not found: %v", err))
		return
	}

	remoteFile, err := sftpClient.Open(remotePath)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to open remote file: %v", err))
		return
	}
	defer remoteFile.Close()

	scanner := bufio.NewScanner(remoteFile)
	var lines []string
	count := 0
	for scanner.Scan() && count < linesLimit {
		lines = append(lines, scanner.Text())
		count++
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"success":    true,
		"filename":   filepath.Base(remotePath),
		"path":       remotePath,
		"size":       stat.Size(),
		"size_human": formatBytes(stat.Size()),
		"lines_read": len(lines),
		"content":    strings.Join(lines, "\n"),
	})
}

func main() {
	initConfig()

	mux := http.NewServeMux()
	mux.HandleFunc("/", handleHealth)
	mux.HandleFunc("/health", handleHealth)
	mux.HandleFunc("/api/sftp/test", authMiddleware(handleTest))
	mux.HandleFunc("/api/sftp/list", authMiddleware(handleList))
	mux.HandleFunc("/api/sftp/download", authMiddleware(handleDownload))
	mux.HandleFunc("/api/sftp/content", authMiddleware(handleContent))

	addr := ":" + cfg.Port
	log.Printf("==================================================")
	log.Printf("🚀 SFTP Relay Service running on port %s", cfg.Port)
	log.Printf("📍 Target SFTP Server : %s:%d (User: %s)", cfg.SFTPHost, cfg.SFTPPort, cfg.SFTPUser)
	log.Printf("🔑 API Key Protected : %s", cfg.APIKey)
	log.Printf("Endpoints:")
	log.Printf("  - GET  /health")
	log.Printf("  - GET  /api/sftp/test")
	log.Printf("  - GET  /api/sftp/list?path=.")
	log.Printf("  - GET  /api/sftp/download?path=/path/to/file.csv")
	log.Printf("  - GET  /api/sftp/content?path=/path/to/file.csv")
	log.Printf("==================================================")

	if err := http.ListenAndServe(addr, mux); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}
