package http

import (
	"encoding/json"
	"errors"
	"mime"
	"net/http"
	"strings"
	"time"

	"github.com/go-ap/activitypub"

	"source.toby3d.me/toby3d/home/internal/common"
	"source.toby3d.me/toby3d/home/internal/domain"
	"source.toby3d.me/toby3d/home/internal/entry"
	"source.toby3d.me/toby3d/home/internal/site"
	"source.toby3d.me/toby3d/home/internal/theme"
	"source.toby3d.me/toby3d/home/internal/urlutil"
)

type (
	Handler struct {
		entries entry.UseCase
		sites   site.UseCase
		themes  theme.UseCase
	}

	Profile struct {
		*activitypub.Person

		// Mastodon related

		// Pinned posts. See [Featured collection].
		//
		// [Featured collection]: https://docs.joinmastodon.org/spec/activitypub/#featured
		Featured []interface{} `json:"featured"`

		// Required for Move activity.
		AlsoKnownAs []string `json:"alsoKnownAs"`

		// Will be shown as a locked account.
		ManuallyApprovesFollowers bool `json:"manuallyApprovesFollowers"`

		// Will be shown in the profile directory.
		// See [Discoverability flag].
		//
		// [Discoverability flag]: https://docs.joinmastodon.org/spec/activitypub/#discoverable
		Discoverable bool `json:"discoverable"`
	}
)

func NewHandler(sites site.UseCase, entries entry.UseCase, themes theme.UseCase) *Handler {
	return &Handler{
		sites:   sites,
		entries: entries,
		themes:  themes,
	}
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	head, tail := urlutil.ShiftPath(r.URL.Path)

	lang := domain.NewLanguage(head)

	s, err := h.sites.Do(r.Context(), lang)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)

		return
	}

	contentType, params, _ := mime.ParseMediaType(r.Header.Get(common.HeaderAccept))

	switch contentType {
	case common.MIMEApplicationLdJSON: // NOTE(toby3d): show entry as ActivityPub object.
		if profile, ok := params["profile"]; ok && profile != "https://www.w3.org/ns/activitystreams" {
			http.Error(w, "got '"+profile+"' profile value, want 'https://www.w3.org/ns/activitystreams'",
				http.StatusBadRequest)

			return
		}

		w.Header().Set(common.HeaderContentType, common.MIMEApplicationActivityJSONCharsetUTF8)
		encoder := json.NewEncoder(w)

		if head == "" { // NOTE(toby3d): base URL point to owner Profile.
			langRef := activitypub.LangRef(lang.Lang())
			person := activitypub.PersonNew(activitypub.IRI(s.BaseURL.String()))
			person.URL = person.ID
			person.Name.Add(activitypub.LangRefValueNew(langRef, "Maxim Lebedev"))
			person.Summary.Add(activitypub.LangRefValueNew(langRef, "Creative dude from russia"))
			person.PreferredUsername.Add(activitypub.LangRefValueNew(langRef, "toby3d"))
			person.Published = time.Date(2009, time.February, 0, 0, 0, 0, 0, time.UTC)

			_ = encoder.Encode(person)

			return
		}

		e, err := h.entries.Do(r.Context(), lang, tail)
		if err != nil {
			if errors.Is(err, entry.ErrNotExist) {
				http.NotFound(w, r)
			} else {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}

			return
		}

		resp := activitypub.ObjectNew(activitypub.NoteType)
		resp.ID = activitypub.ID(s.BaseURL.JoinPath(e.File.Path()).String())
		resp.Content.Add(activitypub.LangRefValueNew(activitypub.NilLangRef, string(e.Content)))

		_ = encoder.Encode(resp)

		return
	}

	// NOTE(toby3d): search entry for requested URL and language
	// code in subdir.
	e, err := h.entries.Do(r.Context(), lang, tail)
	if err != nil {
		if errors.Is(err, entry.ErrNotExist) {
			http.NotFound(w, r)
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}

		return
	}

	// NOTE(toby3d): wrap founded entry into theme template and
	// answer to client.
	contentLanguage := make([]string, len(e.Translations))
	for i := range e.Translations {
		contentLanguage[i] = e.Translations[i].Language.Code()
	}

	w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))

	template, err := h.themes.Do(r.Context(), s, e)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)

		return
	}

	w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
	if err = template(w); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
