package auth

import (
	"errors"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/core/authidentity"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/passwords"
	"github.com/tidbcloud/AgentTeam/internal/service/authflow"
)

// SessionHandler owns token/session authentication flows.
type SessionHandler struct {
	logger                    *slog.Logger
	store                     Store
	authFlow                  AuthFlow
	emailVerificationRequired bool
	sessionTTL                time.Duration
	passwordAuthDisabled      bool
}

// Login handles POST /api/v1/auth/login.
// Authenticates a user by email and password and returns session tokens.
func (handler *SessionHandler) Login(ctx *gin.Context) {
	if handler.passwordAuthDisabled {
		api.Error(ctx, http.StatusServiceUnavailable, "Password auth is disabled")
		return
	}

	var request dto.LoginRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}

	loginIdentifier, err := authidentity.ResolveLoginIdentifier(request.Email)
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, err.Error())
		return
	}
	password := strings.TrimSpace(request.Password)
	if password == "" {
		api.Error(ctx, http.StatusBadRequest, "password is required")
		return
	}

	user, err := handler.store.FindUserByLoginIdentifier(ctx.Request.Context(), loginIdentifier.Value)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			api.Error(ctx, http.StatusUnauthorized, "Invalid credentials")
			return
		}
		api.StoreError(ctx, err)
		return
	}

	passwordOK, err := passwords.Verify(password, user.PasswordHash)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if !passwordOK {
		api.Error(ctx, http.StatusUnauthorized, "Invalid credentials")
		return
	}

	if loginIdentifier.Kind == authidentity.KindEmail && user.EmailVerifiedAt == nil && !handler.emailVerificationRequired {
		user, err = handler.store.MarkUserEmailVerified(ctx.Request.Context(), user.ID)
		if err != nil {
			api.StoreError(ctx, err)
			return
		}
	}
	tryAutoJoinOrganizationsAfterVerifiedLogin(ctx, handler.authFlow, handler.logger, user, loginIdentifier.Kind)

	authResponse, err := createAuthTokensResponse(ctx, handler.authFlow, handler.store, handler.sessionTTL, user, authflow.DefaultWorkspaceOptions{})
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, authResponse)
}

// Logout handles POST /api/v1/auth/logout.
// Revokes the refresh token and current session, including its mobile family.
func (handler *SessionHandler) Logout(ctx *gin.Context) {
	var request dto.LogoutRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}

	refreshToken := strings.TrimSpace(request.RefreshToken)
	if refreshToken != "" {
		if err := handler.store.RevokeRefreshToken(ctx.Request.Context(), refreshToken); err != nil {
			api.StoreError(ctx, err)
			return
		}
	}

	if accessToken, ok := api.BearerToken(ctx); ok {
		authenticated, err := handler.store.AuthenticateSession(ctx.Request.Context(), accessToken)
		if err == nil {
			if err := handler.store.RevokeSession(ctx.Request.Context(), authenticated.Session.ID); err != nil {
				api.StoreError(ctx, err)
				return
			}
		} else if !errors.Is(err, api.ErrUnauthorized) {
			api.StoreError(ctx, err)
			return
		}
	}

	ctx.JSON(http.StatusOK, dto.EmptyResponse{})
}

// Refresh handles POST /api/v1/auth/refresh.
// Rotates the refresh token and issues a new access token pair.
func (handler *SessionHandler) Refresh(ctx *gin.Context) {
	var request dto.LogoutRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}

	refreshToken := strings.TrimSpace(request.RefreshToken)
	if refreshToken == "" {
		api.Error(ctx, http.StatusBadRequest, "refreshToken is required")
		return
	}

	accessToken, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	nextRefreshToken, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}

	now := time.Now().UTC()
	if _, _, err := handler.store.RotateRefreshTokenAndCreateSession(
		ctx.Request.Context(),
		refreshToken,
		secrets.HashToken(nextRefreshToken),
		now.Add(handler.sessionTTL),
		secrets.HashToken(accessToken),
		now.Add(handler.sessionTTL),
	); err != nil {
		api.StoreError(ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, dto.TokenRefreshResponse{
		AccessToken:  accessToken,
		RefreshToken: nextRefreshToken,
	})
}
