package server

import (
	"fmt"
	"html/template"
	"io/fs"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/sessions"

	webassets "gobsidian/web"
	"gobsidian/internal/auth"
	"gobsidian/internal/config"
	"gobsidian/internal/handlers"
	"gobsidian/internal/models"
	"gobsidian/internal/utils"
)

type Server struct {
	config *config.Config
	router *gin.Engine
	store  *sessions.CookieStore
	auth   *auth.Service
}

func New(cfg *config.Config) *Server {
	if !cfg.Debug {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.Default()
	store := sessions.NewCookieStore([]byte(cfg.SecretKey))

	// Initialize auth service (panic on error during startup)
	authSvc, err := auth.Open(cfg)
	if err != nil {
		panic(fmt.Errorf("failed to initialize auth: %w", err))
	}

	s := &Server{
		config: cfg,
		router: router,
		store:  store,
		auth:   authSvc,
	}

	// Global middlewares
	s.router.Use(s.SessionUser())
	// Enforce IP bans/whitelists and log access for every request
	s.router.Use(s.IPBanEnforce())
	s.router.Use(s.AccessLogger())

	s.setupRoutes()
	s.setupStaticFiles()
	s.setupTemplates()

	return s
}

func (s *Server) Start() error {
	// Ensure notes directory exists
	if err := utils.EnsureDir(s.config.NotesDir); err != nil {
		return fmt.Errorf("failed to create notes directory: %w", err)
	}

	// Ensure image storage directory exists for mode 2
	if s.config.ImageStorageMode == 2 {
		if err := utils.EnsureDir(s.config.ImageStoragePath); err != nil {
			return fmt.Errorf("failed to create image storage directory: %w", err)
		}
	}

	// Start background cleanup for access logs older than 7 days (daily)
	go s.startAccessLogCleanup()

	addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
	fmt.Printf("Starting Gobsidian server on %s\n", addr)
	fmt.Printf("Notes directory: %s\n", s.config.NotesDir)

	return s.router.Run(addr)
}

func (s *Server) setupRoutes() {
	h := handlers.New(s.config, s.store, s.auth)
	// Group all routes under optional URL prefix
	r := s.router.Group(s.config.URLPrefix)

	// Main routes
	r.GET("/", h.IndexHandler)
	r.GET("/folder/*path", h.FolderHandler)
	r.GET("/note/*path", h.NoteHandler)

	// File serving routes
	r.GET("/serve_attached_image/*path", h.ServeAttachedImageHandler)
	r.GET("/serve_stored_image/:filename", h.ServeStoredImageHandler)
	r.GET("/download/*path", h.DownloadHandler)
	r.GET("/view_text/*path", h.ViewTextHandler)

	// Auth routes
	r.GET("/editor/login", h.LoginPage)
	r.POST("/editor/login", s.CSRFRequire(), h.LoginPost)
	r.POST("/editor/logout", s.RequireAuth(), s.CSRFRequire(), h.LogoutPost)
	// MFA challenge routes (no auth yet, but CSRF)
	r.GET("/editor/mfa", s.CSRFRequire(), h.MFALoginPage)
	r.POST("/editor/mfa", s.CSRFRequire(), h.MFALoginVerify)

	// New /editor group protected by auth + CSRF
	editor := r.Group("/editor", s.RequireAuth(), s.CSRFRequire())
	{
		editor.GET("/create", h.CreateNotePageHandler)
		editor.POST("/create", h.CreateNoteHandler)
		editor.GET("/edit/*path", h.EditNotePageHandler)
		editor.POST("/edit/*path", h.EditNoteHandler)
		editor.DELETE("/delete/*path", h.DeleteHandler)

		// Text editor routes under /editor
		editor.GET("/edit_text/*path", h.EditTextPageHandler)
		editor.POST("/edit_text/*path", h.PostEditTextHandler)

		// Upload under /editor (secured)
		editor.POST("/upload", h.UploadHandler)

		// Settings under /editor
		editor.GET("/settings", h.SettingsPageHandler)
		editor.GET("/settings/image_storage", h.GetImageStorageSettingsHandler)
		editor.POST("/settings/image_storage", h.PostImageStorageSettingsHandler)
		editor.GET("/settings/notes_dir", h.GetNotesDirSettingsHandler)
		editor.POST("/settings/notes_dir", h.PostNotesDirSettingsHandler)
		editor.GET("/settings/file_extensions", h.GetFileExtensionsSettingsHandler)
		editor.POST("/settings/file_extensions", h.PostFileExtensionsSettingsHandler)
		// Security settings (IP ban thresholds/duration/permanent)
		editor.GET("/settings/security", h.GetSecuritySettingsHandler)
		editor.POST("/settings/security", h.PostSecuritySettingsHandler)

		// Profile
		editor.GET("/profile", h.ProfilePage)
		editor.POST("/profile/password", h.PostProfileChangePassword)
		editor.POST("/profile/email", h.PostProfileChangeEmail)
		editor.POST("/profile/mfa/enable", h.PostProfileEnableMFA)
		editor.POST("/profile/mfa/disable", h.PostProfileDisableMFA)
		// MFA setup during enrollment
		editor.GET("/profile/mfa/setup", h.ProfileMFASetupPage)
		editor.POST("/profile/mfa/verify", h.ProfileMFASetupVerify)

		// Admin dashboard
		editor.GET("/admin", s.RequireAdmin(), h.AdminPage)

		// Admin CRUD API under /editor/admin
		admin := editor.Group("/admin", s.RequireAdmin())
		{
			// Logs page
			admin.GET("/logs", h.AdminLogsPage)
			// Manual clear old access logs (older than 7 days)
			admin.POST("/logs/clear_access", h.AdminClearAccessLogs)
			// Security: IP ban/whitelist actions
			admin.POST("/ip/ban", h.AdminBanIP)
			admin.POST("/ip/unban", h.AdminUnbanIP)
			admin.POST("/ip/whitelist", h.AdminWhitelistIP)
			admin.POST("/users", h.AdminCreateUser)
			admin.DELETE("/users/:id", h.AdminDeleteUser)
			admin.POST("/users/:id/active", h.AdminSetUserActive)
			admin.POST("/users/:id/mfa/enable", h.AdminEnableUserMFA)
			admin.POST("/users/:id/mfa/disable", h.AdminDisableUserMFA)
			admin.POST("/users/:id/mfa/reset", h.AdminResetUserMFA)
			admin.POST("/groups", h.AdminCreateGroup)
			admin.DELETE("/groups/:id", h.AdminDeleteGroup)
			admin.POST("/memberships/add", h.AdminAddUserToGroup)
			admin.POST("/memberships/remove", h.AdminRemoveUserFromGroup)
		}
	}

	// API routes
	r.GET("/api/tree", h.TreeAPIHandler)
	r.GET("/api/search", h.SearchHandler)
}

func (s *Server) setupStaticFiles() {
	// Serve /static from embedded web/static
	sub, err := fs.Sub(webassets.StaticFS, "static")
	if err != nil {
		panic(err)
	}
	s.router.StaticFS(s.config.URLPrefix+"/static", http.FS(sub))
	// Favicon from same sub FS
	s.router.StaticFileFS(s.config.URLPrefix+"/favicon.ico", "favicon.ico", http.FS(sub))
}

func (s *Server) setupTemplates() {
	// Add template functions
	funcMap := template.FuncMap{
		"formatSize": utils.FormatFileSize,
		"formatTime": utils.FormatTime,
		"join":       strings.Join,
		// base returns the configured URL prefix ("" for root)
		"base": func() string { return s.config.URLPrefix },
		// url joins the base with the given path ensuring single slash
		"url": func(p string) string {
			bp := s.config.URLPrefix
			if bp == "" {
				if p == "" { return "" }
				if strings.HasPrefix(p, "/") { return p }
				return "/" + p
			}
			if p == "" || p == "/" {
				return bp + "/"
			}
			if strings.HasPrefix(p, "/") {
				return bp + p
			}
			return bp + "/" + p
		},
		"contains": func(slice []string, item string) bool {
			for _, s := range slice {
				if s == item {
					return true
				}
			}
			return false
		},
		"add": func(a, b int) int {
			return a + b
		},
		"sub": func(a, b int) int {
			return a - b
		},
		"fileTypeClass": func(fileType models.FileType) string {
			switch fileType {
			case models.FileTypeMarkdown:
				return "text-blue-400"
			case models.FileTypeDirectory:
				return "text-yellow-400"
			case models.FileTypeImage:
				return "text-green-400"
			case models.FileTypeText:
				return "text-gray-400"
			default:
				return "text-gray-500"
			}
		},
		"fileTypeIcon": func(fileType models.FileType) string {
			switch fileType {
			case models.FileTypeMarkdown:
				return "📝"
			case models.FileTypeDirectory:
				return "📁"
			case models.FileTypeImage:
				return "🖼️"
			case models.FileTypeText:
				return "📄"
			default:
				return "📄"
			}
		},
		"dict": func(values ...interface{}) map[string]interface{} {
			if len(values)%2 != 0 {
				return nil
			}
			dict := make(map[string]interface{}, len(values)/2)
			for i := 0; i < len(values); i += 2 {
				key, ok := values[i].(string)
				if !ok {
					return nil
				}
				dict[key] = values[i+1]
			}
			return dict
		},
		"safeHTML": func(s string) template.HTML {
			return template.HTML(s)
		},
	}

	// Load templates from embedded FS
	tplFS, err := fs.Sub(webassets.TemplatesFS, "templates")
	if err != nil {
		panic(err)
	}
	templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tplFS, "*.html"))
	s.router.SetHTMLTemplate(templates)

	fmt.Printf("DEBUG: Templates loaded successfully\n")
}

// startAccessLogCleanup deletes access logs older than 7 days once at startup and then daily.
func (s *Server) startAccessLogCleanup() {
	// initial cleanup
	_, _ = s.auth.DB.Exec(`DELETE FROM access_logs WHERE created_at < DATETIME('now', '-7 days')`)
	ticker := time.NewTicker(24 * time.Hour)
	for range ticker.C {
		_, _ = s.auth.DB.Exec(`DELETE FROM access_logs WHERE created_at < DATETIME('now', '-7 days')`)
	}
}
