package server

import (
	"context"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"

	"scrabble/backend/internal/accountdelete"
	"scrabble/backend/internal/game"
)

// Account deletion is legal retention, not erasure (docs/ARCHITECTURE.md §9.1): the account
// row survives as a tombstone while its credentials are journalled + freed, its live
// surfaces anonymised, and its own social/ephemeral data dropped. Messages are kept. The
// step-up is a mailed code for an account with a confirmed email, or a typed phrase for a
// platform-only account (possession-proof is unattainable there, so the phrase is
// anti-impulse only).

// deletePhrase is the fixed confirmation phrase a no-email account types to delete.
// Compared case-insensitively; the client localises only the surrounding instruction.
const deletePhrase = "DELETE"

// deleteRequestResponse tells the client which step-up the account uses.
type deleteRequestResponse struct {
	Method string `json:"method"` // "email" | "phrase"
}

// handleRequestDelete starts account deletion: it mails a delete code to an account with a
// confirmed email, or reports the typed-phrase path otherwise.
func (s *Server) handleRequestDelete(c *gin.Context) {
	uid, ok := userID(c)
	if !ok {
		abortBadRequest(c, "missing identity")
		return
	}
	ctx := c.Request.Context()
	hasEmail, err := s.emails.HasEmail(ctx, uid)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	if !hasEmail {
		c.JSON(http.StatusOK, deleteRequestResponse{Method: "phrase"})
		return
	}
	if err := s.emails.RequestDeleteCode(ctx, uid); err != nil {
		s.abortErr(c, err)
		return
	}
	c.JSON(http.StatusOK, deleteRequestResponse{Method: "email"})
}

// deleteConfirmBody carries the step-up proof: a mailed code (email account) or the typed
// phrase (platform-only account).
type deleteConfirmBody struct {
	Code   string `json:"code"`
	Phrase string `json:"phrase"`
}

// handleConfirmDelete verifies the step-up and deletes the account.
func (s *Server) handleConfirmDelete(c *gin.Context) {
	uid, ok := userID(c)
	if !ok {
		abortBadRequest(c, "missing identity")
		return
	}
	var req deleteConfirmBody
	if err := c.ShouldBindJSON(&req); err != nil {
		abortBadRequest(c, "invalid request body")
		return
	}
	ctx := c.Request.Context()
	hasEmail, err := s.emails.HasEmail(ctx, uid)
	if err != nil {
		s.abortErr(c, err)
		return
	}
	if hasEmail {
		if err := s.emails.VerifyDeleteCode(ctx, uid, req.Code); err != nil {
			s.abortErr(c, err)
			return
		}
	} else if !strings.EqualFold(strings.TrimSpace(req.Phrase), deletePhrase) {
		abortBadRequest(c, "confirmation phrase does not match")
		return
	}
	if err := s.deleteAccount(ctx, uid); err != nil {
		s.abortErr(c, err)
		return
	}
	c.JSON(http.StatusOK, okResponse{OK: true})
}

// deleteAccount runs the deletion orchestration after the step-up passed: it resigns the
// account's active games (so opponents are not stranded and robot games end cleanly),
// drops its all-robot games, tombstones + anonymises the account (journalling and freeing
// its credentials), and revokes its sessions. The tombstone is the point of no return —
// its failure aborts; the game cleanup and session revocation around it are best-effort.
func (s *Server) deleteAccount(ctx context.Context, uid uuid.UUID) error {
	deleter := accountdelete.NewDeleter(s.db)
	if s.games != nil {
		if games, err := s.games.ListForAccount(ctx, uid); err == nil {
			for _, g := range games {
				if g.Status != game.StatusActive {
					continue
				}
				if _, err := s.games.Resign(ctx, g.ID, uid); err != nil {
					s.log.Warn("delete: resign game failed", zap.String("game", g.ID.String()), zap.Error(err))
				}
			}
		} else {
			s.log.Warn("delete: list games failed", zap.Error(err))
		}
	}
	if _, err := deleter.DropAllRobotGames(ctx, uid); err != nil {
		s.log.Warn("delete: drop all-robot games failed", zap.Error(err))
	}
	if err := deleter.AnonymizeAndTombstone(ctx, uid); err != nil {
		return err
	}
	if s.sessions != nil {
		if err := s.sessions.RevokeAllForAccount(ctx, uid); err != nil {
			s.log.Warn("delete: revoke sessions failed", zap.Error(err))
		}
	}
	return nil
}
