package webserver

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"path"
	"sort"
	"strings"
	"time"

	"index-go/config"
	"index-go/core"
	"index-go/utils"
)

// Server 提供内置 Web 目录索引
type Server struct {
	engine     *core.IndexEngine
	cfg        *config.Config
	address    string
	displayURL string
}

// New 创建 WebServer 实例
func New(engine *core.IndexEngine, cfg *config.Config) *Server {
	addr := strings.TrimSpace(cfg.WebServer.Address)
	if addr == "" {
		addr = config.DefaultWebAddress
	}

	return &Server{
		engine:     engine,
		cfg:        cfg,
		address:    addr,
		displayURL: utils.FormatWebAddress(addr),
	}
}

// Address 返回监听地址
func (s *Server) Address() string {
	return s.address
}

// DisplayURL 返回用于提示的访问地址
func (s *Server) DisplayURL() string {
	return s.displayURL
}

// Start 启动 HTTP 服务器
func (s *Server) Start() error {
	mux := http.NewServeMux()
	mux.HandleFunc("/refresh", s.handleRefresh)
	mux.HandleFunc("/", s.handleDirectory)

	utils.IndexLog("Web 索引服务监听: %s", s.address)
	return http.ListenAndServe(s.address, mux)
}

var pageTemplate = template.Must(template.New("index").Parse(`<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{{.Title}}</title>
<style>
body { margin:0; background:#eef2f7; }
.wrapper { max-width: 1100px; margin: 0 auto; padding: 32px 20px 48px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color:#0f172a; }
.nav-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; font-weight:500; }
.nav-bar .breadcrumb { margin-bottom:0; display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
.nav-bar button { margin-left:16px; cursor:pointer; padding:8px 16px; border-radius:6px; border:none; background:#2563eb; color:#fff; font-size:0.9rem; box-shadow:0 4px 10px rgba(37,99,235,0.18); }
.breadcrumb a { color:#2563eb; text-decoration:none; font-size:14px; }
.breadcrumb a:hover { text-decoration:underline; }
.table-card { background:#fff; border-radius:14px; border:1px solid #e5e9f2; overflow:hidden; }
table { width:100%; border-collapse: collapse; }
th { background:#f8fafc; text-align:left; font-size:0.9rem; padding:14px 18px; color:#475569; }
td { padding:14px 18px; border-top:1px solid #eff2f7; font-size:0.95rem; }
tr:nth-child(even) td { background:#fcfdff; }
a { color:#0f172a; text-decoration:none; }
a:hover { text-decoration:underline; }
.empty { padding:32px; text-align:center; color:#94a3b8; }
</style>
</head>
<body>
<div class="wrapper">
  <div class="nav-bar">
    <div class="breadcrumb">
      {{range $index, $crumb := .Breadcrumbs}}
        <a href="{{$crumb.URL}}">{{$crumb.Name}}</a>{{if lt $index $.LastBreadcrumbIndex}}›{{end}}
      {{end}}
    </div>
    <form method="post" action="/refresh">
      <input type="hidden" name="path" value="{{.CurrentPath}}"/>
      <button type="submit">刷新索引</button>
    </form>
  </div>

  <section class="table-card">
    <table>
      <tr><th>名称</th><th>类型</th><th>大小</th><th>最后修改</th></tr>
      {{if .HasEntries}}
        {{if .HasParent}}
        <tr><td><a href="{{.ParentURL}}">返回上级</a></td><td>目录</td><td>—</td><td>—</td></tr>
        {{end}}
        {{range .Directories}}
          <tr><td><a href="{{.URL}}">{{.Name}}/</a></td><td>目录</td><td>—</td><td>—</td></tr>
        {{end}}
        {{range .Files}}
          <tr><td><a href="{{.URL}}" target="_blank" rel="noopener">{{.Name}}</a></td><td>文件</td><td>{{.Size}}</td><td>{{.Modified}}</td></tr>
        {{end}}
      {{else}}
        <tr><td colspan="4" class="empty">当前目录为空</td></tr>
      {{end}}
    </table>
  </section>
</div>
</body>
</html>`))

type breadcrumb struct {
	Name string
	URL  string
}

type dirEntry struct {
	Name string
	URL  string
}

type fileEntry struct {
	Name     string
	URL      string
	Size     string
	Modified string
}

type pageData struct {
	Title               string
	CurrentPath         string
	Breadcrumbs         []breadcrumb
	LastBreadcrumbIndex int
	Directories         []dirEntry
	Files               []fileEntry
	HasEntries          bool
	HasParent           bool
	ParentURL           string
}

func (s *Server) handleDirectory(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	if r.URL.Path != "/" && !strings.HasSuffix(r.URL.Path, "/") {
		http.Redirect(w, r, r.URL.Path+"/", http.StatusPermanentRedirect)
		return
	}

	cleaned := path.Clean(r.URL.Path)
	if strings.Contains(cleaned, "..") {
		http.NotFound(w, r)
		return
	}

	var segments []string
	if cleaned != "/" {
		parts := strings.Split(strings.Trim(cleaned, "/"), "/")
		for _, part := range parts {
			trimmed := strings.TrimSpace(part)
			if trimmed == "" || trimmed == "." || trimmed == ".." {
				http.NotFound(w, r)
				return
			}
			segments = append(segments, trimmed)
		}
	}

	node, ok := s.engine.GetDirectory(segments)
	if !ok {
		http.NotFound(w, r)
		return
	}

	dirs, files := listEntries(segments, node, s.cfg)
	breadcrumbs := buildBreadcrumbs(segments)
	parentURL := "/"
	hasParent := len(segments) > 0
	if hasParent {
		parentURL = buildPathURL(segments[:len(segments)-1])
	}

	data := pageData{
		Title:               buildTitle(segments),
		CurrentPath:         ensureTrailingSlash(cleaned),
		Breadcrumbs:         breadcrumbs,
		LastBreadcrumbIndex: len(breadcrumbs) - 1,
		Directories:         dirs,
		Files:               files,
		HasEntries:          len(dirs) > 0 || len(files) > 0,
		HasParent:           hasParent,
		ParentURL:           parentURL,
	}

	if err := pageTemplate.Execute(w, data); err != nil {
		http.Error(w, fmt.Sprintf("渲染页面失败: %v", err), http.StatusInternalServerError)
	}
}

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

	redirectPath := r.FormValue("path")
	if redirectPath == "" {
		redirectPath = "/"
	}

	ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
	defer cancel()

	if err := s.engine.Refresh(ctx); err != nil {
		utils.IndexLog("刷新索引失败: %v", err)
		http.Error(w, fmt.Sprintf("刷新索引失败: %v", err), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, redirectPath, http.StatusSeeOther)
}

func listEntries(base []string, node core.IndexNode, cfg *config.Config) ([]dirEntry, []fileEntry) {
	var dirs []dirEntry
	var files []fileEntry

	for name, raw := range node {
		switch value := raw.(type) {
		case core.IndexNode:
			dirs = append(dirs, dirEntry{
				Name: name,
				URL:  buildPathURL(append(base, name)),
			})
		case *core.FileDetails:
			files = append(files, fileEntry{
				Name:     name,
				URL:      utils.GetFileURL(value.Key, cfg.Bucket, cfg.Region, cfg.CustomDomain),
				Size:     utils.FormatSize(value.Size),
				Modified: formatTime(value.LastModified),
			})
		}
	}

	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name < dirs[j].Name })
	sort.Slice(files, func(i, j int) bool { return files[i].Name < files[j].Name })

	return dirs, files
}

func buildTitle(segments []string) string {
	if len(segments) == 0 {
		return "根目录"
	}
	return segments[len(segments)-1]
}

func ensureTrailingSlash(p string) string {
	if p == "" {
		return "/"
	}
	if !strings.HasSuffix(p, "/") {
		return p + "/"
	}
	return p
}

func formatTime(value string) string {
	parsed, err := time.Parse(time.RFC3339, value)
	if err != nil {
		return value
	}
	return parsed.Local().Format("2006-01-02 15:04:05")
}

func buildBreadcrumbs(segments []string) []breadcrumb {
	crumbs := []breadcrumb{{Name: "根目录", URL: "/"}}
	for i := range segments {
		crumbs = append(crumbs, breadcrumb{
			Name: segments[i],
			URL:  buildPathURL(segments[:i+1]),
		})
	}
	return crumbs
}

func buildPathURL(segments []string) string {
	if len(segments) == 0 {
		return "/"
	}

	encoded := make([]string, len(segments))
	for i, seg := range segments {
		encoded[i] = url.PathEscape(seg)
	}
	return "/" + strings.Join(encoded, "/") + "/"
}
