// src/services/userService.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { UserProfile, Address } from '../types';

// --- Hoisted Mocks ---
const mocks = vi.hoisted(() => {
  // Create mock implementations for the repository methods we'll be using.
  const mockUpsertAddress = vi.fn();
  const mockUpdateUserProfile = vi.fn();

  return {
    // Mock the withTransaction helper to immediately execute the callback.
    mockWithTransaction: vi.fn().mockImplementation(async (callback) => {
      return callback({});
    }),
    // FIX: Change this from an arrow function to a standard function
    // so that it can be instantiated with `new`.
    MockUserRepository: vi.fn(function () {
      return {
        updateUserProfile: mockUpdateUserProfile,
      };
    }),
    // Expose the method mocks for assertions.
    mockUpsertAddress,
    mockUpdateUserProfile,
  };
});

// --- Mock Modules ---

vi.mock('./db/index.db', () => ({
  withTransaction: mocks.mockWithTransaction,
}));

// This mock is correct, using a standard function for the constructor.
vi.mock('./db/address.db', () => {
  return {
    // We define this as a standard function (not an arrow function)
    // so that it can be instantiated with 'new AddressRepository()'
    AddressRepository: vi.fn(function () {
      return {
        upsertAddress: mocks.mockUpsertAddress,
      };
    }),
  };
});

// This now correctly uses the hoisted mock which is a valid constructor.
vi.mock('./db/user.db', () => ({
  UserRepository: mocks.MockUserRepository,
}));

vi.mock('./logger.server', () => ({
  // Provide a default mock for the logger
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));

// Import the service to be tested AFTER all mocks are set up.
import { userService } from './userService';

describe('UserService', () => {
  beforeEach(() => {
    // Clear call history for all mocks before each test.
    vi.clearAllMocks();
  });

  describe('upsertUserAddress', () => {
    it('should create a new address and link it to a user who has no address', async () => {
      const { logger } = await import('./logger.server');
      // Arrange: A user profile without an existing address_id.
      const user: UserProfile = { user_id: 'user-123', address_id: null } as UserProfile;
      const addressData: Partial<Address> = { address_line_1: '123 New St', city: 'Newville' };

      // Mock the address repository to return a new address ID.
      const newAddressId = 99;
      mocks.mockUpsertAddress.mockResolvedValue(newAddressId);

      // Act: Call the service method.
      const result = await userService.upsertUserAddress(user, addressData, logger);

      // Assert
      expect(result).toBe(newAddressId);
      // 1. Verify the transaction helper was called.
      expect(mocks.mockWithTransaction).toHaveBeenCalledTimes(1);
      // 2. Verify the address was upserted with the correct data.
      expect(mocks.mockUpsertAddress).toHaveBeenCalledWith({
        ...addressData,
        address_id: undefined, // user.address_id was null, so it should be undefined.
      }, logger);
      // 3. Verify the user's profile was updated to link the new address ID.
      expect(mocks.mockUpdateUserProfile).toHaveBeenCalledTimes(1);
      expect(mocks.mockUpdateUserProfile).toHaveBeenCalledWith('user-123', { address_id: newAddressId }, logger);
    });

    it('should update an existing address and NOT link it if the ID does not change', async () => {
      const { logger } = await import('./logger.server');
      // Arrange: A user profile with an existing address_id.
      const existingAddressId = 42;
      const user: UserProfile = { user_id: 'user-123', address_id: existingAddressId } as UserProfile;
      const addressData: Partial<Address> = { address_line_1: '123 Updated St', city: 'Updateville' };

      // Mock the address repository to return the SAME address ID.
      mocks.mockUpsertAddress.mockResolvedValue(existingAddressId);

      // Act: Call the service method.
      const result = await userService.upsertUserAddress(user, addressData, logger);

      // Assert
      expect(result).toBe(existingAddressId);
      // 1. Verify the transaction helper was called.
      expect(mocks.mockWithTransaction).toHaveBeenCalledTimes(1);
      // 2. Verify the address was upserted with the existing ID.
      expect(mocks.mockUpsertAddress).toHaveBeenCalledWith({
        ...addressData,
        address_id: existingAddressId,
      }, logger);
      // 3. Since the address ID did not change, the user profile should NOT be updated.
      expect(mocks.mockUpdateUserProfile).not.toHaveBeenCalled();
    });
  });
});