//go:build !lite

package service

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"breadbox/internal/appconfig"
)

// DevModeSettingsResponse is the read shape for Settings → Developer.
// UploadToken is masked (never plaintext) and nil when unset.
type DevModeSettingsResponse struct {
	Enabled     bool    `json:"enabled"`
	GithubRepo  string  `json:"github_repo"`
	UploadToken *string `json:"upload_token,omitempty"`
}

// UpdateDevModeSettingsParams holds the writable Developer-Mode settings.
// Nil = leave untouched. An empty GithubRepo clears it (falls back to the
// default). UploadToken: nil or empty = leave the stored token alone; non-empty
// = replace it (stored encrypted).
type UpdateDevModeSettingsParams struct {
	Enabled     *bool
	GithubRepo  *string
	UploadToken *string
}

// GetDevModeSettings reads the devmode.* keys from app_config. The upload token
// is decrypted then masked for display; the plaintext never leaves the server.
func (s *Service) GetDevModeSettings(ctx context.Context) (*DevModeSettingsResponse, error) {
	resp := &DevModeSettingsResponse{
		Enabled:    appconfig.Bool(ctx, s.Queries, appconfig.KeyDevModeEnabled, false),
		GithubRepo: appconfig.String(ctx, s.Queries, appconfig.KeyDevModeGithubRepo, appconfig.DevModeDefaultRepo),
	}
	if len(s.EncryptionKey) > 0 {
		tok, _, err := appconfig.ReadEncrypted(ctx, s.Queries, appconfig.KeyDevModeUploadToken, s.EncryptionKey)
		if err != nil {
			return nil, fmt.Errorf("read devmode upload token: %w", err)
		}
		resp.UploadToken = maskToken(tok)
	}
	return resp, nil
}

// DevModeEnabled is a cheap single-key read used by the base-layout middleware
// to decide whether to render the floating reporter.
func (s *Service) DevModeEnabled(ctx context.Context) bool {
	return appconfig.Bool(ctx, s.Queries, appconfig.KeyDevModeEnabled, false)
}

// UpdateDevModeSettings validates and writes the non-nil fields, then returns
// the new state.
func (s *Service) UpdateDevModeSettings(ctx context.Context, p UpdateDevModeSettingsParams) (*DevModeSettingsResponse, error) {
	if p.Enabled != nil {
		if err := s.Queries.SetAppConfig(ctx, appconfigParam(appconfig.KeyDevModeEnabled, strconv.FormatBool(*p.Enabled))); err != nil {
			return nil, fmt.Errorf("set devmode_enabled: %w", err)
		}
	}
	if p.GithubRepo != nil {
		repo := strings.TrimSuffix(strings.TrimSpace(*p.GithubRepo), "/")
		if repo != "" {
			if _, _, err := splitOwnerRepo(repo); err != nil {
				return nil, err
			}
		}
		if err := s.Queries.SetAppConfig(ctx, appconfigParam(appconfig.KeyDevModeGithubRepo, repo)); err != nil {
			return nil, fmt.Errorf("set devmode_github_repo: %w", err)
		}
	}
	if p.UploadToken != nil {
		if tok := strings.TrimSpace(*p.UploadToken); tok != "" {
			if len(s.EncryptionKey) == 0 {
				return nil, fmt.Errorf("%w: ENCRYPTION_KEY must be set to store the upload token", ErrInvalidParameter)
			}
			if err := appconfig.WriteEncrypted(ctx, s.Queries, appconfig.KeyDevModeUploadToken, tok, s.EncryptionKey); err != nil {
				return nil, fmt.Errorf("write devmode upload token: %w", err)
			}
		}
	}
	return s.GetDevModeSettings(ctx)
}

// splitOwnerRepo parses "owner/repo", rejecting blanks, extra slashes, and
// whitespace. Returned parts are safe to interpolate into a GitHub URL.
func splitOwnerRepo(repo string) (owner, name string, err error) {
	repo = strings.TrimSpace(repo)
	repo = strings.TrimSuffix(repo, "/")
	parts := strings.Split(repo, "/")
	if len(parts) != 2 {
		return "", "", fmt.Errorf("%w: repository must be in owner/repo form", ErrInvalidParameter)
	}
	owner, name = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
	if owner == "" || name == "" || strings.ContainsAny(repo, " \t") {
		return "", "", fmt.Errorf("%w: repository must be in owner/repo form", ErrInvalidParameter)
	}
	return owner, name, nil
}
