package web

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

	"infinite-noodle/internal/assets"
	"infinite-noodle/internal/noodle"
)

func StaticFiles() fs.FS {
	return assets.FS
}

type indexPageData struct {
	Username string
	Role     string
	ClientIP string
	Noodles  []noodle.Noodle
}

type userConfigPageData struct {
	Username string
	Role     string
	Users    []noodle.User
	Error    string
}

func HandleMain(db *noodle.Database, pc *chan noodle.Noodle, auth *Auth) func(w http.ResponseWriter, req *http.Request) {
	tmpl, err := template.ParseFS(assets.FS, "templates/*.html")
	if err != nil {
		log.Fatalf("Error parsing templates: %v", err)
	}
	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		data := indexPageData{
			Username: currentUser.Username,
			Role:     currentUser.Role,
			ClientIP: clientIPFromRequest(req),
			Noodles:  db.GetAll(),
		}
		if err := tmpl.ExecuteTemplate(w, "index.html", data); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func HandleUsers(auth *Auth, db *noodle.Database) func(w http.ResponseWriter, req *http.Request) {
	tmpl, err := template.ParseFS(assets.FS, "templates/*.html")
	if err != nil {
		log.Fatalf("Error parsing templates: %v", err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		data := userConfigPageData{
			Username: currentUser.Username,
			Role:     currentUser.Role,
		}
		if currentUser.Role == noodle.UserRoleAdmin {
			data.Users = db.GetAllUsers()
		}
		if err := tmpl.ExecuteTemplate(w, "users.html", data); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func HandleAddUser(auth *Auth, db *noodle.Database) func(w http.ResponseWriter, req *http.Request) {
	tmpl, err := template.ParseFS(assets.FS, "templates/*.html")
	if err != nil {
		log.Fatalf("Error parsing templates: %v", err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		username := strings.TrimSpace(req.FormValue("username"))
		password := req.FormValue("password")
		role := normalizeUserRole(req.FormValue("role"))
		if username == "" || password == "" {
			renderUsersPage(w, tmpl, db, currentUser, "username and password are required", http.StatusBadRequest)
			return
		}
		if role == "" {
			renderUsersPage(w, tmpl, db, currentUser, "invalid user role", http.StatusBadRequest)
			return
		}

		user := noodle.User{
			Id:           db.MakeID(),
			Username:     username,
			Role:         role,
			PasswordHash: HashPassword(password),
			CreatedAt:    time.Now().UTC(),
		}
		if err := db.AddUser(user); err != nil {
			renderUsersPage(w, tmpl, db, currentUser, err.Error(), http.StatusBadRequest)
			return
		}

		http.Redirect(w, req, "/users", http.StatusSeeOther)
	}
}

func HandleSetUserRole(auth *Auth, db *noodle.Database) func(w http.ResponseWriter, req *http.Request) {
	tmpl, err := template.ParseFS(assets.FS, "templates/*.html")
	if err != nil {
		log.Fatalf("Error parsing templates: %v", err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		username := strings.TrimSpace(req.FormValue("username"))
		role := normalizeUserRole(req.FormValue("role"))
		if username == "" {
			renderUsersPage(w, tmpl, db, currentUser, "username is required", http.StatusBadRequest)
			return
		}
		if role == "" {
			renderUsersPage(w, tmpl, db, currentUser, "invalid user role", http.StatusBadRequest)
			return
		}

		if _, err := db.SetUserRole(username, role); err != nil {
			renderUsersPage(w, tmpl, db, currentUser, err.Error(), http.StatusBadRequest)
			return
		}

		http.Redirect(w, req, "/users", http.StatusSeeOther)
	}
}

func HandleChangePassword(auth *Auth, db *noodle.Database) func(w http.ResponseWriter, req *http.Request) {
	tmpl, err := template.ParseFS(assets.FS, "templates/*.html")
	if err != nil {
		log.Fatalf("Error parsing templates: %v", err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		targetUsername := strings.TrimSpace(req.FormValue("username"))
		password := req.FormValue("password")
		confirmPassword := req.FormValue("confirm_password")
		if targetUsername == "" || password == "" {
			renderUsersPage(w, tmpl, db, currentUser, "username and password are required", http.StatusBadRequest)
			return
		}
		if confirmPassword != "" && password != confirmPassword {
			renderUsersPage(w, tmpl, db, currentUser, "password confirmation does not match", http.StatusBadRequest)
			return
		}
		if currentUser.Role != noodle.UserRoleAdmin && targetUsername != currentUser.Username {
			http.Error(w, "cannot change another user's password", http.StatusForbidden)
			return
		}

		if _, err := db.SetUserPassword(targetUsername, HashPassword(password)); err != nil {
			renderUsersPage(w, tmpl, db, currentUser, err.Error(), http.StatusBadRequest)
			return
		}

		http.Redirect(w, req, "/users", http.StatusSeeOther)
	}
}

func HandleDeleteUser(auth *Auth, db *noodle.Database) func(w http.ResponseWriter, req *http.Request) {
	tmpl, err := template.ParseFS(assets.FS, "templates/*.html")
	if err != nil {
		log.Fatalf("Error parsing templates: %v", err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}
		if currentUser.Role != noodle.UserRoleAdmin {
			http.Error(w, "admin access required", http.StatusForbidden)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		username := strings.TrimSpace(req.FormValue("username"))
		if username == "" {
			renderUsersPage(w, tmpl, db, currentUser, "username is required", http.StatusBadRequest)
			return
		}

		if _, err := db.DeleteUser(username); err != nil {
			renderUsersPage(w, tmpl, db, currentUser, err.Error(), http.StatusBadRequest)
			return
		}

		if username == currentUser.Username {
			auth.ClearSession(w)
			http.Redirect(w, req, "/login", http.StatusSeeOther)
			return
		}

		http.Redirect(w, req, "/users", http.StatusSeeOther)
	}
}

func HandleDelete(db *noodle.Database, pc *chan noodle.Noodle) func(w http.ResponseWriter, req *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		id := strings.TrimSpace(req.FormValue("id"))
		if id == "" {
			http.Error(w, "missing noodle id", http.StatusBadRequest)
			return
		}

		item, err := db.DeleteByID(id)
		if err != nil {
			http.Error(w, "noodle not found", http.StatusNotFound)
			return
		}
		item.IsUp = false
		*pc <- item
		log.Printf("Deleting noodle=%v", item)

		http.Redirect(w, req, "/", http.StatusTemporaryRedirect)
	}
}

func HandleAdd(db *noodle.Database, pc *chan noodle.Noodle, auth *Auth) func(w http.ResponseWriter, req *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		currentUser, _ := auth.CurrentUser(req)
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		listenPort, err := strconv.Atoi(strings.TrimSpace(req.FormValue("listen_port")))
		if err != nil {
			http.Error(w, "invalid listen port", http.StatusBadRequest)
			return
		}
		if !isValidPort(listenPort) {
			http.Error(w, "listen port must be between 1 and 65535", http.StatusBadRequest)
			return
		}

		destPort, err := strconv.Atoi(strings.TrimSpace(req.FormValue("dest_port")))
		if err != nil {
			http.Error(w, "invalid destination port", http.StatusBadRequest)
			return
		}
		if !isValidPort(destPort) {
			http.Error(w, "destination port must be between 1 and 65535", http.StatusBadRequest)
			return
		}

		expiration, err := time.ParseDuration(strings.TrimSpace(req.FormValue("expiration")))
		if err != nil {
			http.Error(w, "invalid expiration duration", http.StatusBadRequest)
			return
		}

		proto := strings.ToUpper(strings.TrimSpace(req.FormValue("proto")))
		if proto != "TCP" && proto != "UDP" {
			http.Error(w, "invalid protocol", http.StatusBadRequest)
			return
		}

		src, err := normalizeSourceList(req.FormValue("src"))
		if err != nil {
			http.Error(w, "invalid source restriction", http.StatusBadRequest)
			return
		}

		destHost := strings.TrimSpace(req.FormValue("dest_host"))
		if destHost == "" {
			http.Error(w, "destination host is required", http.StatusBadRequest)
			return
		}

		item := noodle.Noodle{
			Id:         db.MakeID(),
			Name:       strings.TrimSpace(req.FormValue("name")),
			Proto:      proto,
			Src:        src,
			ListenPort: listenPort,
			DestPort:   destPort,
			DestHost:   destHost,
			Expiration: expiration,
			IsUp:       true,
			CreatedBy:  currentUser.Username,
		}
		if err := db.Add(item); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		*pc <- item

		http.Redirect(w, req, "/", http.StatusTemporaryRedirect)
	}
}

func HandleToggle(db *noodle.Database, pc *chan noodle.Noodle) func(w http.ResponseWriter, req *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(w, "", http.StatusMethodNotAllowed)
			return
		}

		if err := req.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		id := strings.TrimSpace(req.FormValue("id"))
		if id == "" {
			http.Error(w, "missing noodle id", http.StatusBadRequest)
			return
		}

		item, err := db.SetIsUp(id, req.FormValue("is_up") == "on")
		if err != nil {
			http.Error(w, "noodle not found", http.StatusNotFound)
			return
		}
		*pc <- item

		http.Redirect(w, req, "/", http.StatusTemporaryRedirect)
	}
}

func clientIPFromRequest(req *http.Request) string {
	forwardedFor := strings.TrimSpace(req.Header.Get("X-Forwarded-For"))
	if forwardedFor != "" {
		parts := strings.Split(forwardedFor, ",")
		ip := strings.TrimSpace(parts[0])
		if parsed := net.ParseIP(ip); parsed != nil {
			return parsed.String()
		}
	}

	realIP := strings.TrimSpace(req.Header.Get("X-Real-Ip"))
	if realIP != "" {
		if parsed := net.ParseIP(realIP); parsed != nil {
			return parsed.String()
		}
	}

	host, _, err := net.SplitHostPort(strings.TrimSpace(req.RemoteAddr))
	if err == nil {
		if parsed := net.ParseIP(host); parsed != nil {
			return parsed.String()
		}
	}

	if parsed := net.ParseIP(strings.TrimSpace(req.RemoteAddr)); parsed != nil {
		return parsed.String()
	}

	return "127.0.0.1"
}

func isValidPort(port int) bool {
	return port >= 1 && port <= 65535
}

func normalizeSourceList(raw string) (string, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "", nil
	}
	if strings.EqualFold(raw, "All") {
		return "All", nil
	}

	parts := strings.Split(raw, ",")
	normalized := make([]string, 0, len(parts))
	for _, part := range parts {
		entry := strings.TrimSpace(part)
		if entry == "" {
			return "", net.InvalidAddrError("empty source")
		}

		if strings.Contains(entry, "/") {
			_, network, err := net.ParseCIDR(entry)
			if err != nil {
				return "", err
			}
			normalized = append(normalized, network.String())
			continue
		}

		parsed := net.ParseIP(entry)
		if parsed == nil {
			return "", net.InvalidAddrError(entry)
		}
		normalized = append(normalized, parsed.String())
	}

	return strings.Join(normalized, ", "), nil
}

func normalizeUserRole(role string) string {
	switch strings.ToLower(strings.TrimSpace(role)) {
	case noodle.UserRoleAdmin:
		return noodle.UserRoleAdmin
	case noodle.UserRoleRegular:
		return noodle.UserRoleRegular
	default:
		return ""
	}
}

func renderUsersPage(w http.ResponseWriter, tmpl *template.Template, db *noodle.Database, currentUser authUser, message string, status int) {
	w.WriteHeader(status)
	data := userConfigPageData{
		Username: currentUser.Username,
		Role:     currentUser.Role,
		Error:    message,
	}
	if currentUser.Role == noodle.UserRoleAdmin {
		data.Users = db.GetAllUsers()
	}
	if err := tmpl.ExecuteTemplate(w, "users.html", data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
