package auth

import (
	"context"
	"time"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/client"
	domainSession "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/session"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/usecase/session"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/utils"
)

type Service struct {
	apiClient       *client.Client
	createSessionUC *session.CreateUseCase
	getSessionUC    *session.GetByIdUseCase
	deleteSessionUC *session.DeleteUseCase
	saveSessionUC   *session.SaveUseCase
	logger          *zap.Logger
}

// ProvideService creates the auth service for Wire
func ProvideService(
	apiClient *client.Client,
	createSessionUC *session.CreateUseCase,
	getSessionUC *session.GetByIdUseCase,
	deleteSessionUC *session.DeleteUseCase,
	saveSessionUC *session.SaveUseCase,
	logger *zap.Logger,
) *Service {
	svc := &Service{
		apiClient:       apiClient,
		createSessionUC: createSessionUC,
		getSessionUC:    getSessionUC,
		deleteSessionUC: deleteSessionUC,
		saveSessionUC:   saveSessionUC,
		logger:          logger.Named("auth-service"),
	}

	// Set up token refresh callback to persist new tokens to session
	apiClient.OnTokenRefresh(func(accessToken, refreshToken, accessTokenExpiryDate string) {
		svc.handleTokenRefresh(accessToken, refreshToken, accessTokenExpiryDate)
	})

	return svc
}

// handleTokenRefresh is called when the API client automatically refreshes the access token
func (s *Service) handleTokenRefresh(accessToken, refreshToken, accessTokenExpiryDate string) {
	// Get the current session
	existingSession, err := s.getSessionUC.Execute()
	if err != nil {
		s.logger.Error("Failed to get session during token refresh callback", zap.Error(err))
		return
	}

	if existingSession == nil {
		s.logger.Warn("No session found during token refresh callback")
		return
	}

	// Update the session with new tokens
	existingSession.AccessToken = accessToken
	existingSession.RefreshToken = refreshToken

	// Parse the actual expiry date from the response instead of using hardcoded value
	if accessTokenExpiryDate != "" {
		expiryTime, parseErr := time.Parse(time.RFC3339, accessTokenExpiryDate)
		if parseErr != nil {
			s.logger.Warn("Failed to parse access token expiry date, using default 15m",
				zap.String("expiry_date", accessTokenExpiryDate),
				zap.Error(parseErr))
			existingSession.ExpiresAt = time.Now().Add(15 * time.Minute)
		} else {
			existingSession.ExpiresAt = expiryTime
			s.logger.Debug("Using actual token expiry from response",
				zap.Time("expiry_time", expiryTime))
		}
	} else {
		s.logger.Warn("No access token expiry date in refresh response, using default 15m")
		existingSession.ExpiresAt = time.Now().Add(15 * time.Minute)
	}

	// Save updated session
	if err := s.saveSessionUC.Execute(existingSession); err != nil {
		s.logger.Error("Failed to save session after token refresh", zap.Error(err))
		return
	}

	s.logger.Info("Session updated with refreshed tokens", zap.String("email", utils.MaskEmail(existingSession.Email)))
}

// RequestOTT requests a one-time token for login
func (s *Service) RequestOTT(ctx context.Context, email string) error {
	_, err := s.apiClient.RequestOTT(ctx, email)
	if err != nil {
		s.logger.Error("Failed to request OTT", zap.Error(err))
		return err
	}
	s.logger.Info("OTT requested successfully", zap.String("email", utils.MaskEmail(email)))
	return nil
}

// VerifyOTT verifies the one-time token and returns the encrypted challenge
func (s *Service) VerifyOTT(ctx context.Context, email, ott string) (*client.VerifyOTTResponse, error) {
	resp, err := s.apiClient.VerifyOTT(ctx, email, ott)
	if err != nil {
		s.logger.Error("OTT verification failed", zap.Error(err))
		return nil, err
	}
	s.logger.Info("OTT verified successfully", zap.String("email", utils.MaskEmail(email)))
	return resp, nil
}

// CompleteLogin completes the login process with OTT and challenge
func (s *Service) CompleteLogin(ctx context.Context, input *client.CompleteLoginInput) (*client.LoginResponse, error) {
	// Complete login via API
	resp, err := s.apiClient.CompleteLogin(ctx, input)
	if err != nil {
		s.logger.Error("Login failed", zap.Error(err))
		return nil, err
	}

	// Parse expiration time from response
	var expiresIn time.Duration
	if resp.AccessTokenExpiryDate != "" {
		expiryTime, parseErr := time.Parse(time.RFC3339, resp.AccessTokenExpiryDate)
		if parseErr != nil {
			s.logger.Warn("Failed to parse access token expiry date, using default 15m",
				zap.String("expiry_date", resp.AccessTokenExpiryDate),
				zap.Error(parseErr))
			expiresIn = 15 * time.Minute // Default to 15 minutes (backend default)
		} else {
			expiresIn = time.Until(expiryTime)
			s.logger.Info("Parsed access token expiry",
				zap.Time("expiry_time", expiryTime),
				zap.Duration("expires_in", expiresIn))
		}
	} else {
		s.logger.Warn("No access token expiry date in response, using default 15m")
		expiresIn = 15 * time.Minute // Default to 15 minutes (backend default)
	}

	// Use email as userID for now (can be improved later)
	userID := input.Email

	// Save session locally via use case
	err = s.createSessionUC.Execute(
		userID,
		input.Email,
		resp.AccessToken,
		resp.RefreshToken,
		expiresIn,
	)
	if err != nil {
		s.logger.Error("Failed to save session", zap.Error(err))
		return nil, err
	}

	s.logger.Info("User logged in successfully", zap.String("email", utils.MaskEmail(input.Email)))
	return resp, nil
}

// Logout removes the local session
func (s *Service) Logout(ctx context.Context) error {
	// Delete local session
	err := s.deleteSessionUC.Execute()
	if err != nil {
		s.logger.Error("Failed to delete session", zap.Error(err))
		return err
	}

	s.logger.Info("User logged out successfully")
	return nil
}

// GetCurrentSession retrieves the current user session
func (s *Service) GetCurrentSession(ctx context.Context) (*domainSession.Session, error) {
	sess, err := s.getSessionUC.Execute()
	if err != nil {
		s.logger.Error("Failed to get session", zap.Error(err))
		return nil, err
	}
	return sess, nil
}

// UpdateSession updates the current session
func (s *Service) UpdateSession(ctx context.Context, sess *domainSession.Session) error {
	return s.saveSessionUC.Execute(sess)
}

// IsLoggedIn checks if a user is currently logged in
func (s *Service) IsLoggedIn(ctx context.Context) (bool, error) {
	sess, err := s.getSessionUC.Execute()
	if err != nil {
		return false, err
	}
	if sess == nil {
		return false, nil
	}
	return sess.IsValid(), nil
}

// RestoreSession restores tokens to the API client from a persisted session
// This is used on app startup to resume a session from a previous run
func (s *Service) RestoreSession(ctx context.Context, sess *domainSession.Session) error {
	if sess == nil {
		return nil
	}

	// Restore tokens to API client
	s.apiClient.SetTokens(sess.AccessToken, sess.RefreshToken)
	s.logger.Info("Session restored to API client",
		zap.String("user_id", sess.UserID),
		zap.String("email", utils.MaskEmail(sess.Email)))

	return nil
}

// Register creates a new user account
func (s *Service) Register(ctx context.Context, input *client.RegisterInput) error {
	_, err := s.apiClient.Register(ctx, input)
	if err != nil {
		s.logger.Error("Registration failed", zap.Error(err))
		return err
	}
	s.logger.Info("User registered successfully", zap.String("email", utils.MaskEmail(input.Email)))
	return nil
}

// VerifyEmail verifies the email with the verification code
func (s *Service) VerifyEmail(ctx context.Context, input *client.VerifyEmailInput) error {
	_, err := s.apiClient.VerifyEmailCode(ctx, input)
	if err != nil {
		s.logger.Error("Email verification failed", zap.Error(err))
		return err
	}
	s.logger.Info("Email verified successfully", zap.String("email", utils.MaskEmail(input.Email)))
	return nil
}

// GetAPIClient returns the API client instance
// This allows other parts of the application to make authenticated API calls
func (s *Service) GetAPIClient() *client.Client {
	return s.apiClient
}

// InitiateRecovery initiates the account recovery process
func (s *Service) InitiateRecovery(ctx context.Context, email, method string) (*client.RecoveryInitiateResponse, error) {
	resp, err := s.apiClient.RecoveryInitiate(ctx, email, method)
	if err != nil {
		s.logger.Error("Recovery initiation failed", zap.Error(err))
		return nil, err
	}
	s.logger.Info("Recovery initiated successfully", zap.String("email", utils.MaskEmail(email)))
	return resp, nil
}

// VerifyRecovery verifies the recovery challenge
func (s *Service) VerifyRecovery(ctx context.Context, input *client.RecoveryVerifyInput) (*client.RecoveryVerifyResponse, error) {
	resp, err := s.apiClient.RecoveryVerify(ctx, input)
	if err != nil {
		s.logger.Error("Recovery verification failed", zap.Error(err))
		return nil, err
	}
	s.logger.Info("Recovery verification successful")
	return resp, nil
}

// CompleteRecovery completes the account recovery and resets credentials
func (s *Service) CompleteRecovery(ctx context.Context, input *client.RecoveryCompleteInput) (*client.RecoveryCompleteResponse, error) {
	resp, err := s.apiClient.RecoveryComplete(ctx, input)
	if err != nil {
		s.logger.Error("Recovery completion failed", zap.Error(err))
		return nil, err
	}
	s.logger.Info("Recovery completed successfully")
	return resp, nil
}
