package office

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"strings"
	"time"
)

type PublicShareAccess struct {
	Token    string
	FilePath string
	Password string
}

func (s *Service) PublicEditorConfig(ctx context.Context, token, filePath, mode, password, guestID string) (map[string]any, error) {
	token = strings.TrimSpace(token)
	filePath = normalizePath(filePath)
	if token == "" || filePath == "" {
		return nil, fmt.Errorf("invalid public office session")
	}
	if mode == "" {
		mode = "edit"
	}

	rev, err := s.nc.PublicShareFileRevision(ctx, token, filePath, password)
	if err != nil {
		return nil, fmt.Errorf("resolve public file revision: %w", err)
	}

	apiBase := strings.TrimRight(s.Cfg.APIInternalURL, "/")
	sig, err := signPublicDocAccess(token, filePath, password, s.Cfg.JWTSecret)
	if err != nil {
		return nil, err
	}
	downloadURL := buildPublicOfficeEndpointURL(apiBase, token, "/office/document", filePath, password, sig)
	callbackURL := buildPublicOfficeEndpointURL(apiBase, token, "/office/callback", filePath, password, sig)

	editorUserID := strings.TrimSpace(guestID)
	if editorUserID == "" {
		editorUserID = "public-guest"
	} else {
		editorUserID = "public:" + editorUserID
	}

	config, err := buildEditorConfig(buildEditorConfigInput{
		filePath:     filePath,
		mode:         mode,
		editorUserID: editorUserID,
		userName:     "Invité",
		documentKey:  s.keys.current(rev.FileID),
		downloadURL:  downloadURL,
		callbackURL:  callbackURL,
	})
	if err != nil {
		return nil, err
	}
	return wrapConfig(config, s.Cfg.JWTSecret)
}

func (s *Service) OpenPublicDocument(ctx context.Context, access PublicShareAccess) (io.ReadCloser, string, error) {
	return s.nc.DownloadPublicShare(ctx, access.Token, access.FilePath, access.Password)
}

func (s *Service) SavePublicDocument(ctx context.Context, access PublicShareAccess, body io.Reader, contentType string) error {
	return s.nc.UploadPublicShare(ctx, access.Token, access.FilePath, access.Password, body, contentType)
}

func buildPublicOfficeEndpointURL(base, token, endpoint, filePath, password, sig string) string {
	q := url.Values{}
	q.Set("path", normalizePath(filePath))
	if password != "" {
		q.Set("password", password)
	}
	if sig != "" {
		q.Set("sig", sig)
	}
	return strings.TrimRight(base, "/") + "/api/v1/drive/public/shares/" + url.PathEscape(token) + endpoint + "?" + q.Encode()
}

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)
}

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
}
