package api

import (
	"errors"
	"fmt"
	"net/http"
	"regexp"

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

	"git.mstar.dev/mstar/linstrom/config"
	"git.mstar.dev/mstar/linstrom/shared"
	"git.mstar.dev/mstar/linstrom/storage-new"
	"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
)

var webfingerResourceRegex = regexp.MustCompile(`acct:(?P<username>[\w-]+)@(?<domain>[\w\.-]+)`)

func WellKnownWebfinger(w http.ResponseWriter, r *http.Request) {
	type OutboundLink struct {
		Relation string  `json:"rel"`
		Type     string  `json:"type"`
		Href     *string `json:"href,omitempty"`
	}
	type Outbound struct {
		Subject string         `json:"subject"`
		Links   []OutboundLink `json:"links"`
		Aliases []string       `json:"aliases,omitempty"`
	}
	log := hlog.FromRequest(r)
	requestedResource := r.FormValue("resource")
	matches := webfingerResourceRegex.FindStringSubmatch(requestedResource)
	if len(matches) == 0 {
		webutils.ProblemDetails(
			w,
			http.StatusBadRequest,
			"/errors/webfinger-bad-resource",
			"Bad resource format",
			other.IntoPointer(
				`The "resource" parameter must be available and of the form "acct:<username>@<domain>`,
			),
			nil,
		)
		return
	}
	// NOTE: Safe to access since, if the regex matches, it must include both groups
	// Index 0 is the full matching string
	// Index 1 is the username
	// Index 2 is something vaguely domain-like
	username := matches[1]
	domain := matches[2]
	// Fail if requested user is a different domain
	// TODO: Decide whether to include the info that it's a different domain
	if domain != config.GlobalConfig.General.GetFullDomain() {
		webutils.ProblemDetailsStatusOnly(w, 404)
		return
	}
	user, err := dbgen.User.GetByUsername(username)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			webutils.ProblemDetailsStatusOnly(w, 404)
		} else {
			// Fail the request, then attempt to reconnect
			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 user from db")
			}
		}
		return
	}
	data := Outbound{
		Subject: matches[0],
		Links: []OutboundLink{
			{
				Relation: "self",
				Type:     "application/activity+json",
				Href: other.IntoPointer(
					fmt.Sprintf(
						"%s/api/activitypub/user/%s",
						config.GlobalConfig.General.GetFullPublicUrl(),
						user.ID,
					),
				),
			},
			{
				Relation: "http://webfinger.net/rel/profile-page",
				Type:     "text/html",
				Href: other.IntoPointer(
					fmt.Sprintf(
						"%s/user/%s",
						config.GlobalConfig.General.GetFullPublicUrl(),
						user.ID,
					),
				),
			},
		},
	}
	webutils.SendJson(w, &data)
}

func Nodeinfo(w http.ResponseWriter, r *http.Request) {
	u := dbgen.User
	log := hlog.FromRequest(r)
	userCount, err := u.Where(u.DeletedAt.IsNull(), u.Verified.Is(true)).Count()
	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
	}
	n := dbgen.Note
	noteCount, err := n.Where(n.DeletedAt.IsNull(), n.OriginId.Eq(1)).Count()
	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
	}

	data := map[string]any{
		"version": "2.1",
		"software": map[string]string{
			"name":       "linstrom",
			"version":    shared.Version,
			"homepage":   "https://git.mstar.dev/mstar/linstrom",
			"repository": "https://git.mstar.dev/mstar/linstrom",
		},
		"protocols": []string{"activitypub"},
		"services": map[string]any{
			"inbound":  []string{},
			"outbound": []string{},
		},
		"openRegistrations": config.GlobalConfig.Admin.AllowRegistration,
		"usage": map[string]any{
			"users": map[string]any{
				"total":          userCount,
				"activeHalfyear": nil,
				"activeMonth":    nil,
			},
			"localPosts":    noteCount,
			"localComments": 0,
		},
		"metadata": map[string]any{},
	}
	webutils.SendJson(w, data)
}

func WellKnownNodeinfo(w http.ResponseWriter, r *http.Request) {
	data := map[string]any{
		"links": []map[string]any{
			{
				"rel":  "http://nodeinfo.diaspora.software/ns/schema/2.1",
				"href": config.GlobalConfig.General.GetFullPublicUrl() + "/nodeinfo/2.1",
			},
		},
	}
	webutils.SendJson(w, data)
}
