package forgefed

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"code.gitea.io/gitea/models/federation"
	"code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/log"
	user_service "code.gitea.io/gitea/services/user"

	ap "github.com/go-ap/activitypub"
)

func GetActor(id string) (*ap.Actor, error) {
	client := http.Client{}
	req, err := http.NewRequest("GET", id, nil)
	if err != nil {
		//Handle Error
	}

	req.Header = http.Header{
		"Content-Type": {"application/activity+json"},
	}
	r, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	body, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	actorObj := new(ap.Actor)
	err = json.Unmarshal(body, &actorObj)
	if err != nil {
		return nil, err
	}
	return actorObj, nil
}

func GetPersonAvatar(ctx context.Context, person *ap.Person) ([]byte, error) {

	avatarObj := new(ap.Image)
	ap.CopyItemProperties(avatarObj, person.Icon)
	log.Info("Getting avatar from link : %s", avatarObj.URL.GetLink().String())

	r, err := http.Get(avatarObj.URL.GetLink().String())
	if err != nil {
		log.Error("Got error while fetching avatar fn: %w", err)
		return nil, err
	}
	defer r.Body.Close()
	return io.ReadAll(r.Body)
}

func SavePerson(ctx context.Context, person *ap.Person) (*user.User, error) {

	fmt.Println(person.ID.String())
	hostname, err := GetHostnameFromResource(person.ID.String())

	u, err := federation.CreateFederatedUser(ctx, person.PreferredUsername.String(), person.URL.GetID().String(), hostname)
	if err != nil {
		log.Error("Got error while saving person: %w", err)
		return nil, err
	}

	avatar, err := GetPersonAvatar(ctx, person)
	if err != nil {
		log.Error("Got error while fetching avatar: %w", err)
		return nil, err
	}

	if u.IsUploadAvatarChanged(avatar) {
		_ = user_service.UploadAvatar(ctx, u, avatar)
	}

	return u, nil
}

func GetActorFromUser(ctx context.Context, u *user.User) (*ap.Actor, error) {

	alias := u.Name

	webfingerRes, err := WebFingerLookup(alias)
	if err != nil {
		return nil, err
	}

	actorID := webfingerRes.GetActorLink().Href

	return GetActor(actorID)
}

// Clean up remote actors (persons) without any followers in local instance
func CleanUpRemotePersons(ctx context.Context, olderThan time.Duration) error {
	page := 0
	for {
		users, err := federation.GetRemoteUsersWithNoLocalFollowers(ctx, olderThan, page)
		if len(users) == 0 {
			break
		}
		if err != nil {
			log.Trace("Error: CleanUpRemotePersons: %v", err)
			return err
		}

		for _, u := range users {
			err = user_service.DeleteUser(ctx, &u, false)
			if err != nil {
				log.Trace("Error: CleanUpRemotePersons: %v", err)
				return err
			}
		}
		page += 1
	}
	return nil
}

func UpdatePersonActor(ctx context.Context) error {
	// NOTE: change of any of these don't matter at this point since we are
	// ignoring actor's PreferredUsername and using their address to generate
	// username and email. Ask suggestions from other devs.
	//
	//	fmt.Println(person.ID.String())
	//	hostname, err := GetHostnameFromResource(person.ID.String())
	//
	//
	//	u := new(user.User)
	//	u.Name = "@" + person.PreferredUsername.String() + "@" + hostname
	//	//panic(u.Name)
	//	u.Email = person.PreferredUsername.String() + "@" + hostname
	//	u.Website = person.URL.GetID().String()
	//	u.KeepEmailPrivate = true

	page := 0
	for {
		federatedUsers, err := federation.GetRemotePersons(ctx, page)
		if len(federatedUsers) == 0 {
			break
		}
		if err != nil {
			log.Trace("Error: UpdatePersonActor: %v", err)
			return err
		}

		for _, f := range federatedUsers {
			log.Info("Updating users, got %s", f.ExternalID)
			u, err := user.GetUserByName(ctx, f.ExternalID)
			if err != nil {
				log.Error("Got error while getting user: %w", err)
				return err
			}

			person, err := GetActorFromUser(ctx, u)
			if err != nil {
				log.Error("Got error while fetching actor: %w", err)
				return err
			}

			avatar, err := GetPersonAvatar(ctx, person)
			if err != nil {
				log.Error("Got error while fetching avatar: %w", err)
				return err
			}

			if u.IsUploadAvatarChanged(avatar) {
				_ = user_service.UploadAvatar(ctx, u, avatar)
			}
		}
		page += 1
	}
	return nil
}
