package ultidraw

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

type roomTokenPayload struct {
	Room    string `json:"room"`
	Path    string `json:"path"`
	User    string `json:"user"`
	Sub     string `json:"sub"`
	Name    string `json:"name"`
	Mode    string `json:"mode"`
	Expires int64  `json:"exp"`
}

func signRoomToken(payload roomTokenPayload, secret string) (string, error) {
	if secret == "" {
		return "", nil
	}
	return signJWT(payload, secret)
}

func signJWT(payload any, secret string) (string, error) {
	if secret == "" {
		return "", nil
	}
	header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
	bodyBytes, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	body := base64.RawURLEncoding.EncodeToString(bodyBytes)
	mac := hmac.New(sha256.New, []byte(secret))
	_, _ = mac.Write([]byte(header + "." + body))
	sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	return header + "." + body + "." + sig, nil
}

func verifyJWT(token, secret string) (map[string]any, error) {
	if secret == "" || token == "" {
		return nil, fmt.Errorf("missing token or secret")
	}
	parts := splitJWT(token)
	if len(parts) != 3 {
		return nil, fmt.Errorf("invalid token")
	}
	mac := hmac.New(sha256.New, []byte(secret))
	_, _ = mac.Write([]byte(parts[0] + "." + parts[1]))
	expected := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	if !hmac.Equal([]byte(expected), []byte(parts[2])) {
		return nil, fmt.Errorf("invalid signature")
	}
	raw, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, err
	}
	var payload map[string]any
	if err := json.Unmarshal(raw, &payload); err != nil {
		return nil, err
	}
	return payload, nil
}

func splitJWT(token string) []string {
	var parts []string
	start := 0
	for i := 0; i < len(token); i++ {
		if token[i] == '.' {
			parts = append(parts, token[start:i])
			start = i + 1
		}
	}
	parts = append(parts, token[start:])
	return parts
}

func sha256Hex(b []byte) string {
	sum := sha256.Sum256(b)
	return hexEncode(sum[:])
}

func hexEncode(b []byte) string {
	const hexdigits = "0123456789abcdef"
	out := make([]byte, len(b)*2)
	for i, v := range b {
		out[i*2] = hexdigits[v>>4]
		out[i*2+1] = hexdigits[v&0x0f]
	}
	return string(out)
}

func hashPath(p string) string {
	h := sha256Hex([]byte(normalizePath(p)))
	if len(h) > 16 {
		return h[:16]
	}
	return h
}

func verifyPublicDocAccess(token, filePath, password, sig, secret string) bool {
	if secret == "" {
		return true
	}
	payload, err := verifyJWT(sig, secret)
	if err != nil {
		return false
	}
	if payload["token"] != strings.TrimSpace(token) || payload["path"] != normalizePath(filePath) {
		return false
	}
	if pw, _ := payload["password"].(string); pw != password {
		return false
	}
	if exp, ok := payload["exp"].(float64); ok && int64(exp) < time.Now().Unix() {
		return false
	}
	return true
}

func signPublicDocAccess(token, filePath, password, secret string) (string, error) {
	payload := map[string]any{
		"token":    strings.TrimSpace(token),
		"path":     normalizePath(filePath),
		"password": password,
		"exp":      time.Now().Add(2 * time.Hour).Unix(),
	}
	return signJWT(payload, secret)
}
