package federation

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"strings"

	"xorm.io/xorm"

	"github.com/forgeo/forgebucket/internal/models"
)

// APID returns the canonical ActivityPub actor ID for a local username.
func APID(instanceURL, username string) string {
	return strings.TrimRight(instanceURL, "/") + "/users/" + username
}

// GetOrCreate fetches the FederationActor for a user, creating it with a fresh
// RSA-2048 key pair if none exists. Actor URLs are derived from instanceURL.
func GetOrCreate(db *xorm.Engine, userID int64, username, instanceURL string) (*models.FederationActor, error) {
	var actor models.FederationActor
	if found, _ := db.Where("user_id = ?", userID).Get(&actor); found {
		return &actor, nil
	}

	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, fmt.Errorf("generate rsa key: %w", err)
	}

	privPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(priv),
	})
	pubDER, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
	if err != nil {
		return nil, fmt.Errorf("marshal public key: %w", err)
	}
	pubPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER})

	base := APID(instanceURL, username)
	actor = models.FederationActor{
		UserID:     userID,
		APID:       base,
		InboxURL:   base + "/inbox",
		OutboxURL:  base + "/outbox",
		PublicKey:  string(pubPEM),
		PrivateKey: string(privPEM),
	}
	if _, err := db.Insert(&actor); err != nil {
		// Race condition: another goroutine may have just created it.
		if found, _ := db.Where("user_id = ?", userID).Get(&actor); found {
			return &actor, nil
		}
		return nil, fmt.Errorf("insert actor: %w", err)
	}
	return &actor, nil
}

// ActorJSON builds the JSON-LD actor document returned by GET /users/{username}.
func ActorJSON(actor *models.FederationActor, username, displayName string) map[string]any {
	return map[string]any{
		"@context": []any{
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/v1",
		},
		"id":                actor.APID,
		"type":              "Person",
		"preferredUsername": username,
		"name":              displayName,
		"inbox":             actor.InboxURL,
		"outbox":            actor.OutboxURL,
		"followers":         actor.APID + "/followers",
		"following":         actor.APID + "/following",
		"publicKey": map[string]any{
			"id":           actor.APID + "#main-key",
			"owner":        actor.APID,
			"publicKeyPem": actor.PublicKey,
		},
	}
}
