package federation

import (
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"time"

	"xorm.io/xorm"

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

// Receive persists an inbound activity and dispatches it by type.
// The caller is responsible for verifying the HTTP signature before calling this.
func Receive(db *xorm.Engine, localActor *models.FederationActor, body []byte) error {
	var activity map[string]any
	if err := json.Unmarshal(body, &activity); err != nil {
		return fmt.Errorf("parse activity: %w", err)
	}

	actType, _ := activity["type"].(string)
	actorAPID, _ := activity["actor"].(string)

	entry := &models.FederationActivity{
		ActorAPID:   localActor.APID,
		Type:        actType,
		ObjectJSON:  string(body),
		Direction:   "inbound",
		RemoteActor: actorAPID,
		Published:   time.Now().UTC(),
	}
	db.Insert(entry) //nolint:errcheck

	switch actType {
	case "Follow":
		return handleFollow(db, localActor, activity, actorAPID)
	case "Accept":
		handleAccept(db, localActor, activity)
	case "Undo":
		handleUndo(db, localActor, activity)
	case "Create":
		if IsCreatePullRequest(body) {
			// Derive instanceURL from the local actor's APID.
			instanceURL := extractInstanceURL(localActor.APID)
			if err := HandleCreatePullRequest(db, body, instanceURL); err != nil {
				log.Printf("federation: handle Create(PullRequest): %v", err)
			}
		} else {
			log.Printf("federation: received Create activity from %s (non-PR, skipped)", actorAPID)
		}
	default:
		log.Printf("federation: received unhandled activity type %q from %s", actType, actorAPID)
	}
	return nil
}

// handleFollow auto-accepts all incoming Follow activities and sends an Accept
// back to the sender's inbox.
func handleFollow(db *xorm.Engine, localActor *models.FederationActor, follow map[string]any, followerAPID string) error {
	if followerAPID == "" {
		return fmt.Errorf("Follow activity missing actor field")
	}

	// Fetch the follower's remote actor to get their inbox URL.
	remote, err := FetchActor(db, followerAPID)
	if err != nil {
		return fmt.Errorf("fetch follower actor: %w", err)
	}
	if remote.InboxURL == "" {
		return fmt.Errorf("follower has no inbox URL")
	}

	// Build Accept activity.
	accept := map[string]any{
		"@context": "https://www.w3.org/ns/activitystreams",
		"id":       localActor.APID + "/activities/accept-" + fmt.Sprint(time.Now().UnixNano()),
		"type":     "Accept",
		"actor":    localActor.APID,
		"object":   follow,
	}

	// Deliver asynchronously so inbox handler returns quickly.
	go func() {
		if err := DeliverActivity(localActor, accept, remote.InboxURL); err != nil {
			log.Printf("federation: deliver Accept to %s: %v", remote.InboxURL, err)
			return
		}
		// Store the outbound Accept.
		db.Insert(&models.FederationActivity{ //nolint:errcheck
			ActorAPID:   localActor.APID,
			Type:        "Accept",
			ObjectJSON:  mustJSON(accept),
			Direction:   "outbound",
			RemoteActor: followerAPID,
			Published:   time.Now().UTC(),
		})
		log.Printf("federation: accepted Follow from %s", followerAPID)
	}()

	return nil
}

func handleAccept(db *xorm.Engine, localActor *models.FederationActor, activity map[string]any) {
	// A remote actor accepted our Follow. Nothing to store beyond the inbox entry.
	log.Printf("federation: received Accept for actor %s", localActor.APID)
}

func handleUndo(db *xorm.Engine, localActor *models.FederationActor, activity map[string]any) {
	// Common case: undo a Follow (unfollow).
	obj, _ := activity["object"].(map[string]any)
	if obj == nil {
		return
	}
	if t, _ := obj["type"].(string); t == "Follow" {
		log.Printf("federation: received Undo(Follow) for actor %s", localActor.APID)
	}
}

func mustJSON(v any) string {
	b, _ := json.Marshal(v)
	return string(b)
}

func extractInstanceURL(apid string) string {
	// apid is like "https://example.com/users/alice"
	// Return "https://example.com"
	parts := strings.SplitN(apid, "/", 4)
	if len(parts) >= 3 {
		return parts[0] + "//" + parts[2]
	}
	return apid
}
