import { AdminUser } from '@core/admin/domain/entities/AdminUser';
import { Email } from '@core/admin/domain/value-objects/Email';
import { UserRole } from '@core/admin/domain/value-objects/UserRole';
import { User } from '@core/identity/domain/entities/User';
import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress';
import { PasswordHash } from '@core/identity/domain/value-objects/PasswordHash';
import { UserId } from '@core/identity/domain/value-objects/UserId';
import type { Logger } from '@core/shared/domain/Logger';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

// Import the class we're testing (will be created)
import { SeedDemoUsers } from './SeedDemoUsers';

describe('SeedDemoUsers', () => {
  const originalEnv = { ...process.env };
  let logger: Logger;
  let authRepository: IAuthRepository;
  let passwordHashingService: IPasswordHashingService;
  let adminUserRepository: IAdminUserRepository;

  beforeEach(() => {
    vi.clearAllMocks();
    process.env = { ...originalEnv };
    
    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };

    // Mock password hashing service
    passwordHashingService = {
      hash: vi.fn().mockImplementation(async (plain: string) => {
        return `hashed_${plain}`;
      }),
      verify: vi.fn(),
    };

    // Mock auth repository
    authRepository = {
      findByEmail: vi.fn(),
      save: vi.fn(),
    };

    // Mock admin user repository
    adminUserRepository = {
      findById: vi.fn(),
      findByEmail: vi.fn(),
      emailExists: vi.fn(),
      existsById: vi.fn(),
      existsByEmail: vi.fn(),
      list: vi.fn(),
      count: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      toStored: vi.fn(),
      fromStored: vi.fn(),
    };
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  describe('Demo user specification', () => {
    it('should define the correct demo users with fixed emails', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      const expectedEmails = [
        'demo.driver@example.com',
        'demo.sponsor@example.com',
        'demo.owner@example.com',
        'demo.steward@example.com',
        'demo.admin@example.com',
        'demo.systemowner@example.com',
        'demo.superadmin@example.com',
      ];

      // Mock repositories to return null (users don't exist)
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Verify that findByEmail was called for each expected email
      const calls = (authRepository.findByEmail as any).mock.calls;
      const emailsCalled = calls.map((call: any) => call[0].value);
      
      expect(emailsCalled).toEqual(expect.arrayContaining(expectedEmails));
      expect(emailsCalled.length).toBeGreaterThanOrEqual(7);
    });

    it('should use the fixed password "Demo1234!"', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories to return null (users don't exist)
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Verify password hashing was called with the correct password
      expect(passwordHashingService.hash).toHaveBeenCalledWith('Demo1234!');
    });

    it('should generate deterministic IDs using SeedIdHelper', async () => {
      // Set postgres mode for this test
      process.env.DATABASE_URL = 'postgresql://localhost/test';
      delete process.env.GRIDPILOT_API_PERSISTENCE;
      delete process.env.NODE_ENV;

      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories to return null (users don't exist)
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Verify that users were saved with UUIDs
      const saveCalls = (authRepository.save as any).mock.calls;
      expect(saveCalls.length).toBeGreaterThanOrEqual(7);
      
      // Check that IDs are UUIDs (deterministic from seed keys)
      for (const call of saveCalls) {
        const user: User = call[0];
        const id = user.getId().value;
        // Should be a valid UUID format
        expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
      }
    });

    it('should create primaryDriverId for roles that need it', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories to return null (users don't exist)
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      const saveCalls = (authRepository.save as any).mock.calls;
      
      // Check that all users have primaryDriverId
      const usersWithPrimaryDriverId = saveCalls.filter((call: any) => {
        const user: User = call[0];
        return user.getPrimaryDriverId() !== undefined;
      });

      expect(usersWithPrimaryDriverId.length).toBe(7); // All users
    });
  });

  describe('Idempotency', () => {
    it('should not create duplicates when execute() is called twice', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // First execution - users don't exist
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();
      
      const firstSaveCount = (authRepository.save as any).mock.calls.length;
      const firstAdminCreateCount = (adminUserRepository.create as any).mock.calls.length;

      // Reset mocks
      vi.clearAllMocks();
      
      // Second execution - users now exist
      (authRepository.findByEmail as any).mockImplementation(async (email: EmailAddress) => {
        // Return existing user for all demo emails
        if (email.value.startsWith('demo.')) {
          const displayName = email.value === 'demo.driver@example.com' ? 'John Driver' :
                             email.value === 'demo.sponsor@example.com' ? 'Jane Sponsor' :
                             email.value === 'demo.owner@example.com' ? 'Alice Owner' :
                             email.value === 'demo.steward@example.com' ? 'Bob Steward' :
                             email.value === 'demo.admin@example.com' ? 'Charlie Admin' :
                             email.value === 'demo.systemowner@example.com' ? 'Diana SystemOwner' :
                             'Edward SuperAdmin';
          return User.create({
            id: UserId.create(),
            displayName: displayName,
            email: email.value,
            passwordHash: PasswordHash.fromHash('hashed_Demo1234!'),
          });
        }
        return null;
      });
      
      (adminUserRepository.findByEmail as any).mockImplementation(async (email: Email) => {
        // Return existing admin user for admin roles
        if (email.value.startsWith('demo.owner') || email.value.startsWith('demo.steward') || 
            email.value.startsWith('demo.admin') || email.value.startsWith('demo.systemowner') || 
            email.value.startsWith('demo.superadmin')) {
          const displayName = email.value === 'demo.owner@example.com' ? 'Alice Owner' :
                             email.value === 'demo.steward@example.com' ? 'Bob Steward' :
                             email.value === 'demo.admin@example.com' ? 'Charlie Admin' :
                             email.value === 'demo.systemowner@example.com' ? 'Diana SystemOwner' :
                             'Edward SuperAdmin';
          return AdminUser.create({
            id: UserId.create().value,
            email: email.value,
            roles: ['user'],
            status: 'active',
            displayName: displayName,
          });
        }
        return null;
      });

      await seed.execute();
      
      const secondSaveCount = (authRepository.save as any).mock.calls.length;
      const secondAdminCreateCount = (adminUserRepository.create as any).mock.calls.length;

      // Second execution should not create any new users
      expect(secondSaveCount).toBe(0);
      expect(secondAdminCreateCount).toBe(0);
    });
  });

  describe('Admin user creation', () => {
    it('should create AdminUser entities for admin roles', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Verify admin users were created
      const adminCreateCalls = (adminUserRepository.create as any).mock.calls;
      
      // Should create admin users for: owner, steward, admin, systemowner, superadmin
      expect(adminCreateCalls.length).toBe(5);

      // Verify the roles
      const createdRoles = adminCreateCalls.map((call: any) => {
        const adminUser: AdminUser = call[0];
        return adminUser.roles.map((r: UserRole) => r.value);
      });

      // Each should have appropriate roles
      expect(createdRoles).toContainEqual(['owner']);
      expect(createdRoles).toContainEqual(['user']); // steward
      expect(createdRoles).toContainEqual(['admin']);
      expect(createdRoles).toContainEqual(['admin']); // systemowner
      expect(createdRoles).toContainEqual(['admin']); // superadmin
    });

    it('should not create AdminUser for driver and sponsor roles', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      const adminCreateCalls = (adminUserRepository.create as any).mock.calls;
      
      // Verify no admin users were created for driver and sponsor
      const adminEmails = adminCreateCalls.map((call: any) => call[0].email.value);
      
      expect(adminEmails).not.toContain('demo.driver@example.com');
      expect(adminEmails).not.toContain('demo.sponsor@example.com');
    });
  });

  describe('Persistence detection', () => {
    it('should detect postgres persistence from DATABASE_URL', async () => {
      process.env.DATABASE_URL = 'postgresql://localhost/test';
      delete process.env.GRIDPILOT_API_PERSISTENCE;
      delete process.env.NODE_ENV;

      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Should use UUIDs for IDs (verified by checking ID format)
      const saveCalls = (authRepository.save as any).mock.calls;
      const user: User = saveCalls[0][0];
      const id = user.getId().value;
      
      // UUID format
      expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
    });

    it('should detect inmemory persistence in test environment', async () => {
      process.env.NODE_ENV = 'test';
      delete process.env.DATABASE_URL;
      delete process.env.GRIDPILOT_API_PERSISTENCE;

      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Should use deterministic string IDs
      const saveCalls = (authRepository.save as any).mock.calls;
      const user: User = saveCalls[0][0];
      const id = user.getId().value;
      
      // Should be a deterministic string (not UUID)
      expect(id).toContain('demo-');
    });
  });

  describe('Force reseed support', () => {
    it('should support force reseed via environment variable', async () => {
      process.env.GRIDPILOT_API_FORCE_RESEED = '1';
      process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';

      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock existing users
      const existingUser = User.create({
        id: UserId.create(),
        displayName: 'John Driver',
        email: 'demo.driver@example.com',
        passwordHash: PasswordHash.fromHash('old_hash'),
      });

      (authRepository.findByEmail as any).mockResolvedValue(existingUser);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (authRepository.save as any).mockResolvedValue(undefined);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);

      await seed.execute();

      // Should still save users (force reseed means update existing)
      expect(authRepository.save).toHaveBeenCalled();
    });
  });

  describe('Logging', () => {
    it('should log progress and results', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock repositories
      (authRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.findByEmail as any).mockResolvedValue(null);
      (adminUserRepository.create as any).mockImplementation((user: AdminUser) => user);
      (authRepository.save as any).mockResolvedValue(undefined);

      await seed.execute();

      // Should log start
      expect(logger.info).toHaveBeenCalledWith(
        expect.stringContaining('[Bootstrap] Starting demo users seed')
      );

      // Should log completion
      expect(logger.info).toHaveBeenCalledWith(
        expect.stringContaining('[Bootstrap] Demo users seed completed')
      );
    });

    it('should log when skipping due to existing users', async () => {
      const seed = new SeedDemoUsers(logger, authRepository, passwordHashingService, adminUserRepository);
      
      // Mock existing users
      (authRepository.findByEmail as any).mockImplementation(async (email: EmailAddress) => {
        const displayName = email.value === 'demo.driver@example.com' ? 'John Driver' :
                           email.value === 'demo.sponsor@example.com' ? 'Jane Sponsor' :
                           email.value === 'demo.owner@example.com' ? 'Alice Owner' :
                           email.value === 'demo.steward@example.com' ? 'Bob Steward' :
                           email.value === 'demo.admin@example.com' ? 'Charlie Admin' :
                           email.value === 'demo.systemowner@example.com' ? 'Diana SystemOwner' :
                           'Edward SuperAdmin';
        return User.create({
          id: UserId.create(),
          displayName: displayName,
          email: email.value,
          passwordHash: PasswordHash.fromHash('hashed_Demo1234!'),
        });
      });
      
      (adminUserRepository.findByEmail as any).mockImplementation(async (email: Email) => {
        if (email.value.startsWith('demo.owner') || email.value.startsWith('demo.steward') || 
            email.value.startsWith('demo.admin') || email.value.startsWith('demo.systemowner') || 
            email.value.startsWith('demo.superadmin')) {
          const displayName = email.value === 'demo.owner@example.com' ? 'Alice Owner' :
                             email.value === 'demo.steward@example.com' ? 'Bob Steward' :
                             email.value === 'demo.admin@example.com' ? 'Charlie Admin' :
                             email.value === 'demo.systemowner@example.com' ? 'Diana SystemOwner' :
                             'Edward SuperAdmin';
          return AdminUser.create({
            id: UserId.create().value,
            email: email.value,
            roles: ['user'],
            status: 'active',
            displayName: displayName,
          });
        }
        return null;
      });

      await seed.execute();

      expect(logger.info).toHaveBeenCalledWith(
        expect.stringContaining('[Bootstrap] Demo users already exist, skipping')
      );
    });
  });
});