package handler

import (
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/gommon/log"
)

// IPBlockerMiddleware checks if the requesting IP is blocked
func (h *Handler) IPBlockerMiddleware() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Extract the client IP
			clientIP := ExtractIPFromRequest(
				c.Request().RemoteAddr,
				c.Request().Header.Get("X-Forwarded-For"),
				c.Request().Header.Get("X-Real-IP"),
			)

			// Check if IP is blocked
			isBlocked, blockedIP, err := h.IsIPBlocked(clientIP)
			if err != nil {
				log.Errorf("Error checking blocked IP %s: %v", clientIP, err)
				// Continue on error to avoid breaking the site
				return next(c)
			}

			if isBlocked {
				log.Warnf("Blocked IP %s attempted to access %s", clientIP, c.Path())
				
				// Update last attempt time
				if blockedIP != nil {
					blockedIP.LastAttemptAt = time.Now()
					h.DB.Save(blockedIP)
				}

				// Redirect to 127.0.0.1
				return c.Redirect(http.StatusFound, "http://127.0.0.1")
			}

			return next(c)
		}
	}
}

// SessionAuthMiddleware checks if user is authenticated via session
func (h *Handler) SessionAuthMiddleware() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Skip auth if disabled
			if h.DisableAdminAuth {
				return next(c)
			}

			// Check if authenticated
			if !h.IsAuthenticated(c) {
				// Store the original URL for redirect after login
				originalURL := c.Request().URL.Path
				if c.Request().URL.RawQuery != "" {
					originalURL += "?" + c.Request().URL.RawQuery
				}
				
				// Redirect to login page
				return c.Redirect(http.StatusFound, "/@/login?redirect="+originalURL)
			}

			return next(c)
		}
	}
}

// HTTPSRedirectMiddleware redirects HTTP to HTTPS for admin routes
// Only applies to admin routes (/@/*) and only if HTTPS is available
func (h *Handler) HTTPSRedirectMiddleware() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Only apply to admin routes
			if !strings.HasPrefix(c.Path(), "/@/") {
				return next(c)
			}

			// Skip login page to avoid redirect loop
			if c.Path() == "/@/login" {
				return next(c)
			}

			// Check if already HTTPS
			if h.IsHTTPS(c) {
				return next(c)
			}

			// Check if HTTPS is available by checking X-Forwarded-Proto header exists
			// This indicates we're behind a reverse proxy that supports HTTPS
			if c.Request().Header.Get("X-Forwarded-Proto") != "" {
				// Redirect to HTTPS
				httpsURL := h.GetHTTPSRedirectURL(c)
				return c.Redirect(http.StatusMovedPermanently, httpsURL)
			}

			// No HTTPS available, continue with HTTP
			return next(c)
		}
	}
}

// UpdateAuthMiddleware wraps BasicAuth for update endpoints
// CRITICAL: Only logs failed auth when credentials are ACTUALLY WRONG, not system errors
func (h *Handler) UpdateAuthMiddleware() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Extract credentials
			username, password, ok := c.Request().BasicAuth()
			
			if !ok {
				// No credentials provided - this is NOT a failed auth attempt
				// It's a misconfigured client or direct browser access
				return c.String(http.StatusUnauthorized, "badauth\n")
			}

			// Attempt authentication
			authenticated, authError := h.AuthenticateUpdate(username, password, c)
			
			// If there was a system error (not wrong credentials), don't log as failed auth
			if authError != nil {
				log.Errorf("Authentication system error: %v", authError)
				return c.String(http.StatusUnauthorized, "badauth\n")
			}

			// Only log failed auth if authentication explicitly failed
			// This means: credentials were provided, checked, and found to be WRONG
			if !authenticated {
				clientIP := ExtractIPFromRequest(
					c.Request().RemoteAddr,
					c.Request().Header.Get("X-Forwarded-For"),
					c.Request().Header.Get("X-Real-IP"),
				)
				
				log.Warnf("Failed DynDNS API authentication from IP %s, username: %s", clientIP, username)
				
				// Log the failed attempt (but DON'T trigger IP blocking for API endpoints)
				h.LogFailedAuth(clientIP, c.Request().UserAgent(), c.Path(), username, password)
				
				return c.String(http.StatusUnauthorized, "badauth\n")
			}

			// Authentication successful
			return next(c)
		}
	}
}

// CleanupMiddleware periodically cleans up expired blocks and old records
func (h *Handler) CleanupMiddleware() echo.MiddlewareFunc {
	// Track last cleanup time
	lastCleanup := &time.Time{}
	
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Run cleanup once per hour
			if lastCleanup.IsZero() || time.Since(*lastCleanup) > time.Hour {
				go func() {
					h.CleanupExpiredBlocks()
					h.CleanupOldFailedAuths()
				}()
				now := time.Now()
				lastCleanup = &now
			}
			
			return next(c)
		}
	}
}

// ExtractIPFromRequest safely extracts IP from various headers
func ExtractIPFromRequest(remoteAddr string, xForwardedFor string, xRealIP string) string {
	// Try X-Real-IP first
	if xRealIP != "" {
		ip := net.ParseIP(xRealIP)
		if ip != nil {
			return xRealIP
		}
	}

	// Try X-Forwarded-For
	if xForwardedFor != "" {
		// X-Forwarded-For can contain multiple IPs, get the first one
		ips := splitAndTrim(xForwardedFor, ",")
		if len(ips) > 0 {
			ip := net.ParseIP(ips[0])
			if ip != nil {
				return ips[0]
			}
		}
	}

	// Fall back to RemoteAddr
	ip, _, err := net.SplitHostPort(remoteAddr)
	if err != nil {
		return remoteAddr
	}

	return ip
}

func splitAndTrim(s string, sep string) []string {
	var result []string
	for _, part := range split(s, sep) {
		trimmed := trim(part)
		if trimmed != "" {
			result = append(result, trimmed)
		}
	}
	return result
}

func split(s string, sep string) []string {
	// Simple split implementation
	var result []string
	start := 0
	for i := 0; i < len(s); i++ {
		if string(s[i]) == sep {
			result = append(result, s[start:i])
			start = i + 1
		}
	}
	result = append(result, s[start:])
	return result
}

func trim(s string) string {
	// Simple trim implementation
	start := 0
	end := len(s)
	
	for start < end && (s[start] == ' ' || s[start] == '\t') {
		start++
	}
	
	for end > start && (s[end-1] == ' ' || s[end-1] == '\t') {
		end--
	}
	
	return s[start:end]
}
