package activitypub

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

	webutils "git.mstar.dev/mstar/goutils/http"
	"git.mstar.dev/mstar/goutils/other"
	"git.mstar.dev/mstar/goutils/sliceutils"
	"github.com/rs/zerolog/hlog"

	"git.mstar.dev/mstar/linstrom/activitypub/shared/types"
	"git.mstar.dev/mstar/linstrom/config"
	"git.mstar.dev/mstar/linstrom/storage-new"
	"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
	"git.mstar.dev/mstar/linstrom/storage-new/models"
	webshared "git.mstar.dev/mstar/linstrom/web/shared"
)

func users(w http.ResponseWriter, r *http.Request) {
	type OutboundKey struct {
		Id    string `json:"id"`
		Owner string `json:"owner"`
		Pem   string `json:"publicKeyPem"`
	}
	type OutboundMedia struct {
		Type      string `json:"type"`
		Url       string `json:"url"`
		MediaType string `json:"mediaType"`
	}
	type Outbound struct {
		Context           []any          `json:"@context"`
		Id                string         `json:"id"`
		Type              string         `json:"type"`
		PreferredUsername string         `json:"preferredUsername"`
		Inbox             string         `json:"inbox"`
		PublicKey         OutboundKey    `json:"publicKey"`
		Published         time.Time      `json:"published"`
		DisplayName       string         `json:"name"`
		Description       *string        `json:"summary,omitempty"`
		PublicUrl         string         `json:"url"`
		Icon              *OutboundMedia `json:"icon,omitempty"`
		Banner            *OutboundMedia `json:"image,omitempty"`
		Discoverable      bool           `json:"discoverable"`
		Location          *string        `json:"vcard:Address,omitempty"`
		Birthday          *string        `json:"vcard:bday,omitempty"`
		SpeakAsCat        bool           `json:"speakAsCat"`
		IsCat             bool           `json:"isCat"`
		RestrictedFollow  bool           `json:"manuallyApprovesFollowers"`
	}
	log := hlog.FromRequest(r)
	userId := r.PathValue("id")
	user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).
		Preload(dbgen.User.Icon).Preload(dbgen.User.Banner).
		Preload(dbgen.User.BeingTypes).
		First()
	if err != nil {
		webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
		if storage.HandleReconnectError(err) {
			log.Warn().Msg("Connection to db lost. Reconnect attempt started")
		} else {
			log.Error().Err(err).Msg("Failed to get total user count from db")
		}
		return
	}

	apUrl := userIdToApUrl(user.ID)
	var keyBytes string
	if config.GlobalConfig.Experimental.UseEd25519Keys {
		keyBytes = keyBytesToPem(user.PublicKeyEd)
	} else {
		keyBytes = keyBytesToPem(user.PublicKeyRsa)
	}
	data := Outbound{
		Context:           types.BaseLdContext,
		Id:                apUrl,
		Type:              "Person",
		PreferredUsername: user.Username,
		Inbox:             apUrl + "/inbox",
		PublicKey: OutboundKey{
			Id:    apUrl + "#main-key",
			Owner: apUrl,
			Pem:   keyBytes,
		},
		Published:        user.CreatedAt,
		DisplayName:      user.DisplayName,
		PublicUrl:        config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
		Discoverable:     user.Indexable,
		RestrictedFollow: user.RestrictedFollow,
	}
	if user.Description != "" {
		data.Description = &user.Description
	}
	if user.Icon != nil {
		log.Debug().Msg("icon found")
		data.Icon = &OutboundMedia{
			Type: "Image",
			Url: config.GlobalConfig.General.GetFullPublicUrl() + webshared.EnsurePublicUrl(
				user.Icon.Location,
			),
			MediaType: user.Icon.Type,
		}
	}
	if user.Banner != nil {
		log.Debug().Msg("icon banner")
		data.Banner = &OutboundMedia{
			Type: "Image",
			Url: config.GlobalConfig.General.GetFullPublicUrl() + webshared.EnsurePublicUrl(
				user.Banner.Location,
			),
			MediaType: user.Banner.Type,
		}
	}
	if sliceutils.ContainsFunc(user.BeingTypes, func(t models.UserToBeing) bool {
		return t.Being == models.BEING_CAT
	}) {
		data.IsCat = true
		// data.SpeakAsCat = true // TODO: Move to check of separate field in db model
	}
	if user.Location.Valid {
		data.Location = &user.Location.String
	}
	if user.Birthday.Valid {
		data.Birthday = other.IntoPointer(user.Birthday.Time.Format("2006-Jan-02")) //YYYY-Month-DD
	}

	encoded, err := json.Marshal(data)
	w.Header().Add("Content-Type", "application/activity+json")
	fmt.Fprint(w, string(encoded))
}

func userInbox(w http.ResponseWriter, r *http.Request) {
	log := hlog.FromRequest(r)
	userId := r.PathValue("id")
	data, err := io.ReadAll(r.Body)
	log.Info().Err(err).Str("userId", userId).Bytes("body", data).Msg("Inbox message")
}

/*
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
Fuck you.

If anyone wants to get this shit working *the propper way* with JsonLD, here's the
original code
var chain goap.BaseApChain = &goap.EmptyBaseObject{}
	chain = goap.AppendUDIdData(chain, apUrl)
	chain = goap.AppendUDTypeData(chain, "Person")
	chain = goap.AppendASPreferredNameData(chain, goap.ValueValue[string]{Value: user.DisplayName})
	chain = goap.AppendW3SecurityPublicKeyData(
		chain,
		apUrl+"#main-key",
		apUrl,
		keyBytesToPem(user.PublicKey),
	)

	chainMap := chain.MarshalToMap()
	proc := ld.NewJsonLdProcessor()
	options := ld.NewJsonLdOptions("")
	tmp, tmperr := json.Marshal(chainMap)
	fmt.Println(string(tmp), tmperr)
	data, err := proc.Compact(chainMap, baseLdContext, options)
	// data, err := goap.Compact(chain, baseLdContext)
	if err != nil {
		log.Error().Err(err).Msg("Failed to marshal ap chain")
		webutils.ProblemDetailsStatusOnly(w, 500)
		return
	}
*/
