package webdavserver

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"io"
	"log"
	"math/big"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/net/webdav"
)

// Config holds the configuration for the WebDAV server
type Config struct {
	Host              string
	TCPPort           int
	BasePath          string
	FileSystem        string
	ReadTimeout       time.Duration
	WriteTimeout      time.Duration
	DebugMode         bool
	UseAuth           bool
	Username          string
	Password          string
	UseHTTPS          bool
	CertFile          string
	KeyFile           string
	AutoGenerateCerts bool
	CertValidityDays  int
	CertOrganization  string
}

// Server represents the WebDAV server
type Server struct {
	config     Config
	httpServer *http.Server
	handler    *webdav.Handler
	debugLog   func(format string, v ...interface{})
}

// responseWrapper wraps http.ResponseWriter to capture the status code
type responseWrapper struct {
	http.ResponseWriter
	statusCode int
}

// WriteHeader captures the status code and passes it to the wrapped ResponseWriter
func (rw *responseWrapper) WriteHeader(code int) {
	rw.statusCode = code
	rw.ResponseWriter.WriteHeader(code)
}

// Write captures a 200 status code if WriteHeader hasn't been called yet
func (rw *responseWrapper) Write(b []byte) (int, error) {
	if rw.statusCode == 0 {
		rw.statusCode = http.StatusOK
	}
	return rw.ResponseWriter.Write(b)
}

// NewServer creates a new WebDAV server
func NewServer(config Config) (*Server, error) {
	log.Printf("Creating new WebDAV server with config: host=%s, TCPPort=%d, basePath=%s, fileSystem=%s, debug=%v, auth=%v, https=%v",
		config.Host, config.TCPPort, config.BasePath, config.FileSystem, config.DebugMode, config.UseAuth, config.UseHTTPS)

	// Ensure the file system directory exists
	if err := os.MkdirAll(config.FileSystem, 0755); err != nil {
		log.Printf("ERROR: Failed to create file system directory %s: %v", config.FileSystem, err)
		return nil, fmt.Errorf("failed to create file system directory: %w", err)
	}

	// Log the file system path
	log.Printf("Using file system path: %s", config.FileSystem)

	// Create debug logger function
	debugLog := func(format string, v ...interface{}) {
		if config.DebugMode {
			log.Printf("[WebDAV DEBUG] "+format, v...)
		}
	}

	// Create WebDAV handler
	handler := &webdav.Handler{
		FileSystem: webdav.Dir(config.FileSystem),
		LockSystem: webdav.NewMemLS(),
		Logger: func(r *http.Request, err error) {
			if err != nil {
				log.Printf("WebDAV error: %s %s - %v", r.Method, r.URL.Path, err)
			} else {
				log.Printf("WebDAV: %s %s", r.Method, r.URL.Path)
			}

			// Additional debug logging
			if config.DebugMode {
				log.Printf("[WebDAV DEBUG] Request Headers: %v", r.Header)
				log.Printf("[WebDAV DEBUG] Request RemoteAddr: %s", r.RemoteAddr)
				log.Printf("[WebDAV DEBUG] Request UserAgent: %s", r.UserAgent())
			}
		},
	}

	// Create HTTP server
	httpServer := &http.Server{
		Addr:         fmt.Sprintf("%s:%d", config.Host, config.TCPPort),
		ReadTimeout:  config.ReadTimeout,
		WriteTimeout: config.WriteTimeout,
	}

	return &Server{
		config:     config,
		httpServer: httpServer,
		handler:    handler,
		debugLog:   debugLog,
	}, nil
}

// Start starts the WebDAV server
func (s *Server) Start() error {
	log.Printf("Starting WebDAV server at %s with file system %s", s.httpServer.Addr, s.config.FileSystem)

	// Create a mux to handle the WebDAV requests
	mux := http.NewServeMux()

	// Register the WebDAV handler at the base path
	mux.HandleFunc(s.config.BasePath, func(w http.ResponseWriter, r *http.Request) {
		// Enhanced debug logging
		s.debugLog("Received request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
		s.debugLog("Request Protocol: %s", r.Proto)
		s.debugLog("User-Agent: %s", r.UserAgent())

		// Log all request headers
		for name, values := range r.Header {
			s.debugLog("Header: %s = %s", name, values)
		}

		// Log request depth (important for WebDAV)
		s.debugLog("Depth header: %s", r.Header.Get("Depth"))

		// Add CORS headers
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE")
		w.Header().Set("Access-Control-Allow-Headers", "Depth, Authorization, Content-Type, X-Requested-With")
		w.Header().Set("Access-Control-Max-Age", "86400")

		// Handle OPTIONS requests for CORS and WebDAV discovery
		if r.Method == "OPTIONS" {
			// Add WebDAV specific headers for OPTIONS responses
			w.Header().Set("DAV", "1, 2")
			w.Header().Set("MS-Author-Via", "DAV")
			w.Header().Set("Allow", "OPTIONS, GET, HEAD, POST, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE")

			// Check if this is a macOS WebDAV client
			isMacOSClient := strings.Contains(r.UserAgent(), "WebDAVFS") ||
				strings.Contains(r.UserAgent(), "WebDAVLib") ||
				strings.Contains(r.UserAgent(), "Darwin")

			if isMacOSClient {
				s.debugLog("Detected macOS WebDAV client OPTIONS request, adding macOS-specific headers")
				// These headers help macOS Finder with WebDAV compatibility
				w.Header().Set("X-Dav-Server", "HeroLauncher WebDAV Server")
			}

			w.WriteHeader(http.StatusOK)
			return
		}

		// Handle authentication if enabled
		if s.config.UseAuth {
			s.debugLog("Authentication required for request")
			auth := r.Header.Get("Authorization")

			// Check if this is a macOS WebDAV client
			isMacOSClient := strings.Contains(r.UserAgent(), "WebDAVFS") ||
				strings.Contains(r.UserAgent(), "WebDAVLib") ||
				strings.Contains(r.UserAgent(), "Darwin")

			// Special handling for OPTIONS requests from macOS clients
			if r.Method == "OPTIONS" && isMacOSClient {
				s.debugLog("Detected macOS WebDAV client OPTIONS request, allowing without auth")
				// macOS sends OPTIONS without auth first, we need to let this through
				// but still send the auth challenge
				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"")
				return
			}

			if auth == "" {
				s.debugLog("No Authorization header provided for non-OPTIONS request")
				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"")
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}

			// Parse the authentication header
			if !strings.HasPrefix(auth, "Basic ") {
				s.debugLog("Invalid Authorization header format: %s", auth)
				http.Error(w, "Invalid authorization header", http.StatusBadRequest)
				return
			}

			payload, err := base64.StdEncoding.DecodeString(auth[6:])
			if err != nil {
				s.debugLog("Failed to decode Authorization header: %v, raw header: %s", err, auth)
				http.Error(w, "Invalid authorization header", http.StatusBadRequest)
				return
			}

			pair := strings.SplitN(string(payload), ":", 2)
			if len(pair) != 2 {
				s.debugLog("Invalid credential format: could not split into username:password")
				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"")
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}

			// Log username for debugging (don't log password)
			s.debugLog("Received credentials for user: %s", pair[0])

			if pair[0] != s.config.Username || pair[1] != s.config.Password {
				s.debugLog("Invalid credentials provided, expected user: %s", s.config.Username)
				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"")
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}

			s.debugLog("Authentication successful for user: %s", pair[0])
		}

		// Log request body for WebDAV methods
		if r.Method == "PROPFIND" || r.Method == "PROPPATCH" || r.Method == "REPORT" || r.Method == "PUT" {
			if r.Body != nil {
				bodyBytes, err := io.ReadAll(r.Body)
				if err == nil {
					s.debugLog("Request body: %s", string(bodyBytes))
					// Create a new reader with the same content
					r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
				}
			}
		}

		// Add macOS-specific headers for better compatibility
		isMacOSClient := strings.Contains(r.UserAgent(), "WebDAVFS") ||
			strings.Contains(r.UserAgent(), "WebDAVLib") ||
			strings.Contains(r.UserAgent(), "Darwin")

		if isMacOSClient {
			s.debugLog("Adding macOS-specific headers for better compatibility")
			// These headers help macOS Finder with WebDAV compatibility
			w.Header().Set("MS-Author-Via", "DAV")
			w.Header().Set("X-Dav-Server", "HeroLauncher WebDAV Server")
			w.Header().Set("DAV", "1, 2")

			// Special handling for PROPFIND requests from macOS
			if r.Method == "PROPFIND" {
				s.debugLog("Handling macOS PROPFIND request with special compatibility")
				// Make sure Content-Type is set correctly for PROPFIND responses
				w.Header().Set("Content-Type", "text/xml; charset=utf-8")
			}
		}

		// Create a response wrapper to capture the response
		responseWrapper := &responseWrapper{ResponseWriter: w}

		// Handle WebDAV requests
		s.debugLog("Handling WebDAV request: %s %s", r.Method, r.URL.Path)
		s.handler.ServeHTTP(responseWrapper, r)

		// Log response details
		s.debugLog("Response status: %d", responseWrapper.statusCode)
		s.debugLog("Response content type: %s", w.Header().Get("Content-Type"))

		// Log detailed information for debugging connection issues
		if responseWrapper.statusCode >= 400 {
			s.debugLog("ERROR: WebDAV request failed with status %d", responseWrapper.statusCode)
			s.debugLog("Request method: %s, path: %s", r.Method, r.URL.Path)
			s.debugLog("Response headers: %v", w.Header())
		} else {
			s.debugLog("WebDAV request succeeded with status %d", responseWrapper.statusCode)
		}
	})

	// Set the mux as the HTTP server handler
	s.httpServer.Handler = mux

	// Start the server with HTTPS if configured
	var err error
	if s.config.UseHTTPS {
		// Check if certificate files exist or need to be generated
		if (s.config.CertFile == "" || s.config.KeyFile == "") && !s.config.AutoGenerateCerts {
			log.Printf("ERROR: HTTPS enabled but certificate or key file not provided and auto-generation is disabled")
			return fmt.Errorf("HTTPS enabled but certificate or key file not provided and auto-generation is disabled")
		}

		// Auto-generate certificates if needed
		if (s.config.CertFile == "" || s.config.KeyFile == "" ||
			!fileExists(s.config.CertFile) || !fileExists(s.config.KeyFile)) &&
			s.config.AutoGenerateCerts {

			s.debugLog("Certificate files not found, auto-generating...")

			// Get base directory from the file system path
			baseDir := filepath.Dir(s.config.FileSystem)

			// Create certificates directory if it doesn't exist
			certsDir := filepath.Join(baseDir, "certificates")
			if err := os.MkdirAll(certsDir, 0755); err != nil {
				log.Printf("ERROR: Failed to create certificates directory: %v", err)
				return fmt.Errorf("failed to create certificates directory: %w", err)
			}

			// Set default certificate paths if not provided
			if s.config.CertFile == "" {
				s.config.CertFile = filepath.Join(certsDir, "webdav.crt")
			}
			if s.config.KeyFile == "" {
				s.config.KeyFile = filepath.Join(certsDir, "webdav.key")
			}

			// Generate certificates
			if err := generateCertificate(
				s.config.CertFile,
				s.config.KeyFile,
				s.config.CertOrganization,
				s.config.CertValidityDays,
				s.debugLog,
			); err != nil {
				log.Printf("ERROR: Failed to generate certificates: %v", err)
				return fmt.Errorf("failed to generate certificates: %w", err)
			}

			log.Printf("Successfully generated self-signed certificates at %s and %s",
				s.config.CertFile, s.config.KeyFile)
		}

		// Verify certificate files exist
		if !fileExists(s.config.CertFile) || !fileExists(s.config.KeyFile) {
			log.Printf("ERROR: Certificate files not found at %s and/or %s",
				s.config.CertFile, s.config.KeyFile)
			return fmt.Errorf("certificate files not found")
		}

		// Configure TLS
		tlsConfig := &tls.Config{
			MinVersion: tls.VersionTLS12,
		}
		s.httpServer.TLSConfig = tlsConfig

		log.Printf("Starting WebDAV server with HTTPS on %s using certificates: %s, %s",
			s.httpServer.Addr, s.config.CertFile, s.config.KeyFile)
		err = s.httpServer.ListenAndServeTLS(s.config.CertFile, s.config.KeyFile)
	} else {
		log.Printf("Starting WebDAV server with HTTP on %s", s.httpServer.Addr)
		err = s.httpServer.ListenAndServe()
	}

	if err != nil && err != http.ErrServerClosed {
		log.Printf("ERROR: WebDAV server failed to start: %v", err)
		return err
	}
	return nil
}

// Stop stops the WebDAV server
func (s *Server) Stop() error {
	log.Printf("Stopping WebDAV server at %s", s.httpServer.Addr)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	err := s.httpServer.Shutdown(ctx)
	if err != nil {
		log.Printf("ERROR: Failed to stop WebDAV server: %v", err)
	}
	return err
}

// DefaultConfig returns the default configuration for the WebDAV server
func DefaultConfig() Config {
	// Use system temp directory as default base path
	defaultBasePath := filepath.Join(os.TempDir(), "heroagent")

	return Config{
		Host:              "0.0.0.0",
		TCPPort:           9999,
		BasePath:          "/",
		FileSystem:        defaultBasePath,
		ReadTimeout:       30 * time.Second,
		WriteTimeout:      30 * time.Second,
		DebugMode:         false,
		UseAuth:           false,
		Username:          "admin",
		Password:          "1234",
		UseHTTPS:          false,
		CertFile:          "",
		KeyFile:           "",
		AutoGenerateCerts: true,
		CertValidityDays:  365,
		CertOrganization:  "HeroLauncher WebDAV Server",
	}
}

// fileExists checks if a file exists and is not a directory
func fileExists(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return err == nil && !info.IsDir()
}

// generateCertificate creates a self-signed TLS certificate and key
func generateCertificate(certFile, keyFile, organization string, validityDays int, debugLog func(format string, args ...interface{})) error {
	debugLog("Generating self-signed certificate: certFile=%s, keyFile=%s, organization=%s, validityDays=%d",
		certFile, keyFile, organization, validityDays)

	// Generate private key
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return fmt.Errorf("failed to generate private key: %w", err)
	}

	// Prepare certificate template
	notBefore := time.Now()
	notAfter := notBefore.Add(time.Duration(validityDays) * 24 * time.Hour)

	serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		return fmt.Errorf("failed to generate serial number: %w", err)
	}

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{organization},
			CommonName:   "localhost",
		},
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IPAddresses:           []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
		DNSNames:              []string{"localhost"},
	}

	// Create certificate
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
	if err != nil {
		return fmt.Errorf("failed to create certificate: %w", err)
	}

	// Write certificate to file
	certOut, err := os.Create(certFile)
	if err != nil {
		return fmt.Errorf("failed to open %s for writing: %w", certFile, err)
	}
	defer certOut.Close()

	if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
		return fmt.Errorf("failed to write certificate to file: %w", err)
	}

	// Write private key to file
	keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return fmt.Errorf("failed to open %s for writing: %w", keyFile, err)
	}
	defer keyOut.Close()

	privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
	if err := pem.Encode(keyOut, privateKeyPEM); err != nil {
		return fmt.Errorf("failed to write private key to file: %w", err)
	}

	debugLog("Successfully generated self-signed certificate valid for %d days", validityDays)
	return nil
}
