package keyring

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"

	keyring "github.com/zalando/go-keyring"
)

// ErrNotFound is returned by Load when no token exists for the given account.
var ErrNotFound = errors.New("token not found in keyring")

// Token holds OAuth/OIDC credentials that need to survive app restarts.
type Token struct {
	AccessToken   string    `json:"access_token"`
	RefreshToken  string    `json:"refresh_token,omitempty"`
	ExpiresAt     time.Time `json:"expires_at,omitempty"` // zero = never expires
	UserID        string    `json:"user_id"`
	DeviceID      string    `json:"device_id,omitempty"`
	HomeserverURL string    `json:"homeserver_url"`
	Issuer        string    `json:"issuer,omitempty"`    // MAS/OIDC issuer URL
	ClientID      string    `json:"client_id,omitempty"` // MAS client_id used
}

// KeyringTokenStore persists tokens in the OS keyring (Secret Service on Linux,
// Keychain on macOS, Credential Manager on Windows).
type KeyringTokenStore struct {
	// Service is the keyring namespace. Keep it stable across app versions.
	// Example: "fn_registry.matrix_client_pc"
	Service string
}

// NewKeyringTokenStore returns a store scoped to the given service name.
func NewKeyringTokenStore(service string) *KeyringTokenStore {
	return &KeyringTokenStore{Service: service}
}

// Save serialises t to JSON and writes it to the keyring under (service, account).
// Overwrites silently if an entry already exists.
// account is typically the user ID, e.g. "@user:homeserver.example.com".
func (s *KeyringTokenStore) Save(account string, t Token) error {
	b, err := json.Marshal(t)
	if err != nil {
		return fmt.Errorf("keyring save: marshal: %w", err)
	}
	if err := keyring.Set(s.Service, account, string(b)); err != nil {
		return fmt.Errorf("keyring save: %w", err)
	}
	return nil
}

// Load retrieves and deserialises the token stored under (service, account).
// Returns ErrNotFound if no entry exists. Callers should check with errors.Is.
func (s *KeyringTokenStore) Load(account string) (*Token, error) {
	raw, err := keyring.Get(s.Service, account)
	if err != nil {
		if errors.Is(err, keyring.ErrNotFound) {
			return nil, ErrNotFound
		}
		return nil, fmt.Errorf("keyring load: %w", err)
	}
	var t Token
	if err := json.Unmarshal([]byte(raw), &t); err != nil {
		return nil, fmt.Errorf("keyring load: unmarshal: %w", err)
	}
	return &t, nil
}

// Delete removes the token for account from the keyring.
// Idempotent: if no entry exists, returns nil.
func (s *KeyringTokenStore) Delete(account string) error {
	err := keyring.Delete(s.Service, account)
	if err != nil && !errors.Is(err, keyring.ErrNotFound) {
		return fmt.Errorf("keyring delete: %w", err)
	}
	return nil
}
