package m7s

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"m7s.live/v5/pkg/format"
	"m7s.live/v5/pkg/util"
)

type RedirectAdvisor interface {
	GetRedirectTarget(protocol, streamPath, currentHost string) (targetHost string, statusCode int, ok bool)
}

func (s *Server) GetRedirectAdvisor() RedirectAdvisor {
	if s == nil {
		return nil
	}
	s.redirectOnce.Do(func() {
		s.Plugins.Range(func(plugin *Plugin) bool {
			if advisor, ok := plugin.GetHandler().(RedirectAdvisor); ok {
				s.redirectAdvisor = advisor
				return false
			}
			return true
		})
	})
	return s.redirectAdvisor
}

// RedirectIfNeeded evaluates redirect advice for HTTP-based protocols and issues redirects when appropriate.
// The prefix parameter is the plugin name (e.g., "flv", "mp4", "hls", "webrtc") used to restore the path prefix.
// The redirectPath parameter is the stream path without prefix, used for matching redirect rules.
// This function automatically restores the path prefix (e.g., /flv/, /mp4/) for building the redirect URL.
func (s *Server) RedirectIfNeeded(w http.ResponseWriter, r *http.Request, prefix, redirectPath string) bool {
	if s == nil {
		return false
	}
	advisor := s.GetRedirectAdvisor()
	if advisor == nil {
		return false
	}

	// Save current path and restore it with the prefix for building redirect URL
	currentPath := r.URL.Path
	pathRestored := false

	// Restore path with prefix if needed
	if prefix != "" {
		pathPrefix := "/" + prefix + "/"
		if !strings.HasPrefix(currentPath, pathPrefix) {
			r.URL.Path = pathPrefix + redirectPath
			pathRestored = true
		}
	}

	// Restore original path after redirect (if it was modified)
	defer func() {
		if pathRestored {
			r.URL.Path = currentPath
		}
	}()

	targetHost, statusCode, ok := advisor.GetRedirectTarget(prefix, redirectPath, r.Host)
	if !ok || targetHost == "" {
		return false
	}
	if statusCode == 0 {
		statusCode = http.StatusFound
	}
	// Use r.URL.Path (which has been restored with prefix) to build redirect URL
	redirectURL := buildRedirectURL(r, targetHost)
	http.Redirect(w, r, redirectURL, statusCode)
	return true
}

func buildRedirectURL(r *http.Request, host string) string {
	scheme := requestScheme(r)
	if isWebSocketRequest(r) {
		switch scheme {
		case "https":
			scheme = "wss"
		case "http":
			scheme = "ws"
		}
	}
	target := &url.URL{
		Scheme:   scheme,
		Host:     host,
		Path:     r.URL.Path,
		RawQuery: r.URL.RawQuery,
	}
	return target.String()
}

func requestScheme(r *http.Request) string {
	if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
		return proto
	}
	if r.TLS != nil {
		return "https"
	}
	return "http"
}

func isWebSocketRequest(r *http.Request) bool {
	connection := strings.ToLower(r.Header.Get("Connection"))
	return strings.Contains(connection, "upgrade") && strings.EqualFold(r.Header.Get("Upgrade"), "websocket")
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Check for location-based forwarding first
	if s.Location != nil {
		for pattern, target := range s.Location {
			if pattern.MatchString(r.URL.Path) {
				// Rewrite the URL path and handle locally
				r.URL.Path = pattern.ReplaceAllString(r.URL.Path, target)
				// Forward to local handler
				s.config.HTTP.GetHandler(s.Logger).ServeHTTP(w, r)
				return
			}
		}
	}

	// 检查 admin.zip 是否需要重新加载
	now := time.Now()
	if now.Sub(s.Admin.lastCheckTime) > checkInterval {
		if info, err := os.Stat(s.Admin.FilePath); err == nil && info.ModTime() != s.Admin.zipLastModTime {
			s.Info("admin.zip changed, reloading...")
			s.loadAdminZip()
		}
		s.Admin.lastCheckTime = now
	}

	if s.Admin.zipReader != nil {
		// Handle root path redirect to HomePage
		if r.URL.Path == "/" {
			http.Redirect(w, r, "/admin/#/"+s.Admin.HomePage, http.StatusFound)
			return
		}

		// For .map files, set correct content-type before serving
		if strings.HasSuffix(r.URL.Path, ".map") {
			filePath := strings.TrimPrefix(r.URL.Path, "/admin/")
			file, err := s.Admin.zipReader.Open(filePath)
			if err != nil {
				http.NotFound(w, r)
				return
			}
			defer file.Close()
			w.Header().Set("Content-Type", "application/json")
			io.Copy(w, file)
			return
		}

		http.ServeFileFS(w, r, s.Admin.zipReader, strings.TrimPrefix(r.URL.Path, "/admin"))
		return
	}
	if r.URL.Path == "/favicon.ico" {
		http.ServeFile(w, r, "favicon.ico")
		return
	}
	_, _ = fmt.Fprintf(w, "visit:%s\nMonibuca Engine %s StartTime:%s\n", r.URL.Path, Version, s.StartTime)
	for plugin := range s.Plugins.Range {
		_, _ = fmt.Fprintf(w, "Plugin %s Version:%s\n", plugin.Meta.Name, plugin.Meta.Version)
	}
	for _, api := range s.apiList {
		_, _ = fmt.Fprintf(w, "%s\n", api)
	}
}

func (s *Server) annexB(w http.ResponseWriter, r *http.Request) {
	streamPath := r.PathValue("streamPath")

	if r.URL.RawQuery != "" {
		streamPath += "?" + r.URL.RawQuery
	}
	var conf = s.config.Subscribe
	conf.SubType = SubscribeTypeServer
	conf.SubAudio = false
	suber, err := s.SubscribeWithConfig(r.Context(), streamPath, conf)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var ctx util.HTTP_WS_Writer
	ctx.Conn, err = suber.CheckWebSocket(w, r)
	if err != nil {
		return
	}
	ctx.WriteTimeout = s.GetCommonConf().WriteTimeout
	ctx.ContentType = "application/octet-stream"
	ctx.ServeHTTP(w, r)

	PlayBlock(suber, func(frame *format.RawAudio) (err error) {
		return nil
	}, func(frame *format.AnnexB) (err error) {
		_, err = frame.WriteTo(&ctx)
		if err != nil {
			return
		}
		return ctx.Flush()
	})
}
