package services

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"

	"github.com/breakpilot/consent-service/internal/models"
)

// CreatePasswordResetToken creates a password reset token
func (s *AuthService) CreatePasswordResetToken(ctx context.Context, email, ipAddress string) (string, *uuid.UUID, error) {
	var userID uuid.UUID
	err := s.db.QueryRow(ctx, "SELECT id FROM users WHERE email = $1", email).Scan(&userID)
	if err != nil {
		// Don't reveal if user exists
		return "", nil, nil
	}

	token, err := s.GenerateSecureToken(32)
	if err != nil {
		return "", nil, err
	}

	_, err = s.db.Exec(ctx, `
		INSERT INTO password_reset_tokens (user_id, token, expires_at, ip_address, created_at)
		VALUES ($1, $2, $3, $4, NOW())
	`, userID, token, time.Now().Add(time.Hour), ipAddress)

	if err != nil {
		return "", nil, fmt.Errorf("failed to create reset token: %w", err)
	}

	return token, &userID, nil
}

// ResetPassword resets a user's password using a reset token
func (s *AuthService) ResetPassword(ctx context.Context, token, newPassword string) error {
	var tokenID uuid.UUID
	var userID uuid.UUID
	var expiresAt time.Time
	var usedAt *time.Time

	err := s.db.QueryRow(ctx, `
		SELECT id, user_id, expires_at, used_at FROM password_reset_tokens
		WHERE token = $1
	`, token).Scan(&tokenID, &userID, &expiresAt, &usedAt)

	if err != nil {
		return ErrInvalidToken
	}

	if usedAt != nil || expiresAt.Before(time.Now()) {
		return ErrInvalidToken
	}

	// Hash new password
	passwordHash, err := s.HashPassword(newPassword)
	if err != nil {
		return err
	}

	// Mark token as used
	_, err = s.db.Exec(ctx, `UPDATE password_reset_tokens SET used_at = NOW() WHERE id = $1`, tokenID)
	if err != nil {
		return fmt.Errorf("failed to update token: %w", err)
	}

	// Update password
	_, err = s.db.Exec(ctx, `
		UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2
	`, passwordHash, userID)

	if err != nil {
		return fmt.Errorf("failed to update password: %w", err)
	}

	// Revoke all sessions for security
	_, err = s.db.Exec(ctx, `UPDATE user_sessions SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL`, userID)
	if err != nil {
		fmt.Printf("Warning: failed to revoke sessions: %v\n", err)
	}

	return nil
}

// ChangePassword changes a user's password (requires current password)
func (s *AuthService) ChangePassword(ctx context.Context, userID uuid.UUID, currentPassword, newPassword string) error {
	var passwordHash *string
	err := s.db.QueryRow(ctx, "SELECT password_hash FROM users WHERE id = $1", userID).Scan(&passwordHash)
	if err != nil {
		return ErrUserNotFound
	}

	if passwordHash == nil || !s.VerifyPassword(currentPassword, *passwordHash) {
		return ErrInvalidCredentials
	}

	newPasswordHash, err := s.HashPassword(newPassword)
	if err != nil {
		return err
	}

	_, err = s.db.Exec(ctx, `UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2`, newPasswordHash, userID)
	if err != nil {
		return fmt.Errorf("failed to update password: %w", err)
	}

	return nil
}

// GetUserByID retrieves a user by ID
func (s *AuthService) GetUserByID(ctx context.Context, userID uuid.UUID) (*models.User, error) {
	var user models.User
	err := s.db.QueryRow(ctx, `
		SELECT id, email, name, role, email_verified, email_verified_at, account_status,
		       last_login_at, created_at, updated_at
		FROM users WHERE id = $1
	`, userID).Scan(
		&user.ID, &user.Email, &user.Name, &user.Role, &user.EmailVerified, &user.EmailVerifiedAt,
		&user.AccountStatus, &user.LastLoginAt, &user.CreatedAt, &user.UpdatedAt,
	)

	if err != nil {
		return nil, ErrUserNotFound
	}

	return &user, nil
}

// UpdateProfile updates a user's profile
func (s *AuthService) UpdateProfile(ctx context.Context, userID uuid.UUID, req *models.UpdateProfileRequest) (*models.User, error) {
	_, err := s.db.Exec(ctx, `UPDATE users SET name = $1, updated_at = NOW() WHERE id = $2`, req.Name, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to update profile: %w", err)
	}

	return s.GetUserByID(ctx, userID)
}

// GetActiveSessions retrieves all active sessions for a user
func (s *AuthService) GetActiveSessions(ctx context.Context, userID uuid.UUID) ([]models.UserSession, error) {
	rows, err := s.db.Query(ctx, `
		SELECT id, user_id, device_info, ip_address, user_agent, expires_at, created_at, last_activity_at
		FROM user_sessions
		WHERE user_id = $1 AND revoked_at IS NULL AND expires_at > NOW()
		ORDER BY last_activity_at DESC
	`, userID)

	if err != nil {
		return nil, fmt.Errorf("failed to get sessions: %w", err)
	}
	defer rows.Close()

	var sessions []models.UserSession
	for rows.Next() {
		var session models.UserSession
		err := rows.Scan(
			&session.ID, &session.UserID, &session.DeviceInfo, &session.IPAddress,
			&session.UserAgent, &session.ExpiresAt, &session.CreatedAt, &session.LastActivityAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan session: %w", err)
		}
		sessions = append(sessions, session)
	}

	return sessions, nil
}

// RevokeSession revokes a specific session
func (s *AuthService) RevokeSession(ctx context.Context, userID, sessionID uuid.UUID) error {
	result, err := s.db.Exec(ctx, `
		UPDATE user_sessions SET revoked_at = NOW() WHERE id = $1 AND user_id = $2 AND revoked_at IS NULL
	`, sessionID, userID)

	if err != nil {
		return fmt.Errorf("failed to revoke session: %w", err)
	}

	if result.RowsAffected() == 0 {
		return errors.New("session not found")
	}

	return nil
}

// Logout revokes a session by refresh token
func (s *AuthService) Logout(ctx context.Context, refreshToken string) error {
	tokenHash := s.HashToken(refreshToken)
	_, err := s.db.Exec(ctx, `UPDATE user_sessions SET revoked_at = NOW() WHERE token_hash = $1`, tokenHash)
	return err
}
