package state

import (
	"net/http"
	"time"

	comatproto "github.com/bluesky-social/indigo/api/atproto"
	"github.com/bluesky-social/indigo/atproto/syntax"
	lexutil "github.com/bluesky-social/indigo/lex/util"

	"tangled.org/core/api/tangled"
	"tangled.org/core/appview/db"
	"tangled.org/core/appview/models"
	"tangled.org/core/appview/pages"
	"tangled.org/core/tid"
)

func (s *State) React(w http.ResponseWriter, r *http.Request) {
	l := s.logger.With("handler", "React")
	currentUser := s.oauth.GetMultiAccountUser(r)

	subject := r.FormValue("subject-uri")
	if subject == "" {
		l.Warn("invalid form")
		return
	}

	subjectUri, err := syntax.ParseATURI(subject)
	if err != nil {
		l.Warn("invalid form", "subject", subject, "err", err)
		return
	}

	// override collection NSID to new one
	subjectUri = models.NormalizeReactionSubject(subjectUri)

	reactionKind, ok := models.ParseReactionKind(r.URL.Query().Get("kind"))
	if !ok {
		l.Warn("invalid reaction kind", "kind", r.URL.Query().Get("kind"))
		return
	}

	client, err := s.oauth.AuthorizedClient(r)
	if err != nil {
		l.Error("failed to authorize client", "err", err)
		return
	}

	switch r.Method {
	case http.MethodPost:
		reaction := models.Reaction{
			ReactedByDid: currentUser.Did,
			Rkey:         tid.TID(),
			Kind:         reactionKind,
			ThreadAt:     subjectUri,
			Created:      time.Now(),
		}

		tx, err := s.db.BeginTx(r.Context(), nil)
		if err != nil {
			s.logger.Error("failed to start transaction", "err", err)
			return
		}
		defer tx.Rollback()

		if err := db.UpsertReaction(tx, reaction); err != nil {
			l.Error("db: failed to upsert reaction", "err", err)
			return
		}

		record := reaction.AsRecord()
		resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
			Collection: tangled.FeedReactionNSID,
			Repo:       currentUser.Did,
			Rkey:       reaction.Rkey,
			Record: &lexutil.LexiconTypeDecoder{
				Val: &record,
			},
		})
		if err != nil {
			l.Error("failed to create atproto record", "err", err)
			return
		}
		l.Info("created atproto record", "uri", resp.Uri)

		if err := tx.Commit(); err != nil {
			s.logger.Error("failed to commit transaction", "err", err)
			// DB op failed but record is created in PDS. Ingester will backfill the missed operation
		}

		reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
		if err != nil {
			l.Error("failed to get reactions", "subject", subjectUri)
		}

		s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
			Kind:        reactionKind,
			Count:       reactionMap[reactionKind].Count,
			Users:       reactionMap[reactionKind].Users,
			IsReacted:   true,
			CommentRkey: subjectUri.RecordKey().String(),
			SubjectUri:  subject,
		})

		return
	case http.MethodDelete:
		tx, err := s.db.BeginTx(r.Context(), nil)
		if err != nil {
			l.Error("failed to start transaction", "err", err)
		}
		defer tx.Rollback()

		reactions, err := db.DeleteReaction(tx, syntax.DID(currentUser.Did), subjectUri, reactionKind)
		if err != nil {
			l.Error("failed to delete reactions from db", "err", err)
			return
		}

		var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
		for _, reactionAt := range reactions {
			writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
				RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
					Collection: tangled.FeedReactionNSID,
					Rkey:       reactionAt.RecordKey().String(),
				},
			})
		}
		_, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
			Repo:   currentUser.Did,
			Writes: writes,
		})
		if err != nil {
			l.Error("failed to delete reactions from PDS", "err", err)
			return
		}

		if err := tx.Commit(); err != nil {
			l.Error("failed to commit transaction", "err", err)
			// DB op failed but record is created in PDS. Ingester will backfill the missed operation
		}

		reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
		if err != nil {
			l.Error("failed to get reactions", "subjectUri", subjectUri, "err", err)
			return
		}

		s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
			Kind:        reactionKind,
			Count:       reactionMap[reactionKind].Count,
			Users:       reactionMap[reactionKind].Users,
			IsReacted:   false,
			CommentRkey: subjectUri.RecordKey().String(),
			SubjectUri:  subject,
		})

		return
	}
}
