import { OpenAdminVoteSessionUseCase } from './OpenAdminVoteSessionUseCase';
import { CastAdminVoteUseCase } from './CastAdminVoteUseCase';
import { CloseAdminVoteSessionUseCase } from './CloseAdminVoteSessionUseCase';
import { AdminVoteSession } from '../../domain/entities/AdminVoteSession';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';

// Mock Repository
class MockAdminVoteSessionRepository {
  private sessions: Map<string, AdminVoteSession> = new Map();

  async save(session: AdminVoteSession): Promise<AdminVoteSession> {
    this.sessions.set(session.id, session);
    return session;
  }

  async findById(id: string): Promise<AdminVoteSession | null> {
    return this.sessions.get(id) || null;
  }

  async findActiveForAdmin(adminId: string, leagueId: string): Promise<AdminVoteSession[]> {
    const now = new Date();
    return Array.from(this.sessions.values()).filter(
      s => s.adminId === adminId && 
           s.leagueId === leagueId && 
           s.isVotingWindowOpen(now) &&
           !s.closed
    );
  }

  async findByAdminAndLeague(adminId: string, leagueId: string): Promise<AdminVoteSession[]> {
    return Array.from(this.sessions.values()).filter(
      s => s.adminId === adminId && s.leagueId === leagueId
    );
  }

  async findByLeague(leagueId: string): Promise<AdminVoteSession[]> {
    return Array.from(this.sessions.values()).filter(
      s => s.leagueId === leagueId
    );
  }

  async findClosedUnprocessed(): Promise<AdminVoteSession[]> {
    return Array.from(this.sessions.values()).filter(
      s => s.closed && s.outcome !== undefined
    );
  }
}

class MockRatingEventRepository {
  private events: Map<string, RatingEvent> = new Map();

  async save(event: RatingEvent): Promise<RatingEvent> {
    this.events.set(event.id.value, event);
    return event;
  }

  async findByUserId(userId: string): Promise<RatingEvent[]> {
    return Array.from(this.events.values()).filter(e => e.userId === userId);
  }

  async findByIds(ids: string[]): Promise<RatingEvent[]> {
    return Array.from(this.events.values()).filter(e => ids.includes(e.id.value));
  }

  async getAllByUserId(userId: string): Promise<RatingEvent[]> {
    return Array.from(this.events.values()).filter(e => e.userId === userId);
  }

  async findEventsPaginated(userId: string, options?: import('@core/identity/domain/repositories/IRatingEventRepository').PaginatedQueryOptions): Promise<import('@core/identity/domain/repositories/IRatingEventRepository').PaginatedResult<RatingEvent>> {
    const allEvents = await this.findByUserId(userId);
    
    // Apply filters
    let filtered = allEvents;
    if (options?.filter) {
      const filter = options.filter;
      if (filter.dimensions) {
        filtered = filtered.filter(e => filter.dimensions!.includes(e.dimension.value));
      }
      if (filter.sourceTypes) {
        filtered = filtered.filter(e => filter.sourceTypes!.includes(e.source.type));
      }
      if (filter.from) {
        filtered = filtered.filter(e => e.occurredAt >= filter.from!);
      }
      if (filter.to) {
        filtered = filtered.filter(e => e.occurredAt <= filter.to!);
      }
      if (filter.reasonCodes) {
        filtered = filtered.filter(e => filter.reasonCodes!.includes(e.reason.code));
      }
      if (filter.visibility) {
        filtered = filtered.filter(e => e.visibility.public === (filter.visibility === 'public'));
      }
    }

    const total = filtered.length;
    const limit = options?.limit ?? 10;
    const offset = options?.offset ?? 0;
    const items = filtered.slice(offset, offset + limit);
    const hasMore = offset + limit < total;
    const nextOffset = hasMore ? offset + limit : undefined;

    const result: import('@core/identity/domain/repositories/IRatingEventRepository').PaginatedResult<RatingEvent> = {
      items,
      total,
      limit,
      offset,
      hasMore
    };
    
    if (nextOffset !== undefined) {
      result.nextOffset = nextOffset;
    }

    return result;
  }
}

class MockUserRatingRepository {
  private ratings: Map<string, UserRating> = new Map();

  async save(rating: UserRating): Promise<UserRating> {
    this.ratings.set(rating.userId, rating);
    return rating;
  }

  async findByUserId(userId: string): Promise<UserRating | null> {
    return this.ratings.get(userId) || null;
  }
}

// Mock AppendRatingEventsUseCase
class MockAppendRatingEventsUseCase {
  constructor(
    private ratingEventRepository: any,
    private userRatingRepository: any
  ) {}

  async execute(input: any): Promise<any> {
    const events: RatingEvent[] = [];
    
    // Create events from input
    for (const eventDto of input.events || []) {
      const event = RatingEvent.create({
        id: RatingEventId.generate(),
        userId: eventDto.userId,
        dimension: RatingDimensionKey.create(eventDto.dimension),
        delta: RatingDelta.create(eventDto.delta),
        weight: eventDto.weight,
        occurredAt: new Date(eventDto.occurredAt),
        createdAt: new Date(),
        source: { type: eventDto.sourceType, id: eventDto.sourceId },
        reason: {
          code: eventDto.reasonCode,
          summary: eventDto.reasonSummary,
          details: eventDto.reasonDetails || {},
        },
        visibility: { public: true, redactedFields: [] },
        version: 1,
      });
      events.push(event);
      await this.ratingEventRepository.save(event);
    }

    // Recompute snapshot
    if (events.length > 0) {
      const allEvents = await this.ratingEventRepository.getAllByUserId(input.userId);
      // Simplified snapshot calculation
      const totalDelta = allEvents.reduce((sum, e) => sum + e.delta.value, 0);
      const snapshot = UserRating.create({
        userId: input.userId,
        driver: { value: Math.max(0, Math.min(100, 50 + totalDelta)) },
        adminTrust: { value: 50 },
        stewardTrust: { value: 50 },
        broadcasterTrust: { value: 50 },
        lastUpdated: new Date(),
      });
      await this.userRatingRepository.save(snapshot);
    }

    return {
      events: events.map(e => e.id.value),
      snapshotUpdated: events.length > 0,
    };
  }
}

describe('Admin Vote Session Use Cases', () => {
  let mockSessionRepo: MockAdminVoteSessionRepository;
  let mockEventRepo: MockRatingEventRepository;
  let mockRatingRepo: MockUserRatingRepository;
  let mockAppendUseCase: MockAppendRatingEventsUseCase;

  let openUseCase: OpenAdminVoteSessionUseCase;
  let castUseCase: CastAdminVoteUseCase;
  let closeUseCase: CloseAdminVoteSessionUseCase;

  const now = new Date('2025-01-01T00:00:00Z');
  const tomorrow = new Date('2025-01-02T00:00:00Z');

  let originalDateNow: () => number;

  beforeEach(() => {
    mockSessionRepo = new MockAdminVoteSessionRepository();
    mockEventRepo = new MockRatingEventRepository();
    mockRatingRepo = new MockUserRatingRepository();
    mockAppendUseCase = new MockAppendRatingEventsUseCase(mockEventRepo, mockRatingRepo);

    openUseCase = new OpenAdminVoteSessionUseCase(mockSessionRepo);
    castUseCase = new CastAdminVoteUseCase(mockSessionRepo);
    closeUseCase = new CloseAdminVoteSessionUseCase(
      mockSessionRepo,
      mockEventRepo,
      mockRatingRepo,
      mockAppendUseCase
    );

    // Mock Date.now to return our test time
    originalDateNow = Date.now;
    Date.now = (() => now.getTime()) as any;
  });

  afterEach(() => {
    Date.now = originalDateNow;
  });

  describe('OpenAdminVoteSessionUseCase', () => {
    it('should successfully open a vote session', async () => {
      const input = {
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1', 'user-2', 'user-3'],
      };

      const result = await openUseCase.execute(input);

      expect(result.success).toBe(true);
      expect(result.voteSessionId).toBe('vote-123');

      // Verify session was saved
      const saved = await mockSessionRepo.findById('vote-123');
      expect(saved).toBeDefined();
      expect(saved!.adminId).toBe('admin-789');
      expect(saved!.eligibleVoters).toEqual(['user-1', 'user-2', 'user-3']);
    });

    it('should reject duplicate session IDs', async () => {
      const input = {
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1'],
      };

      await openUseCase.execute(input);
      const result = await openUseCase.execute(input);

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Vote session with this ID already exists');
    });

    it('should reject overlapping sessions', async () => {
      // Create first session
      await openUseCase.execute({
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1'],
      });

      // Try to create overlapping session
      const result = await openUseCase.execute({
        voteSessionId: 'vote-456',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: new Date('2025-01-01T12:00:00Z').toISOString(),
        endDate: new Date('2025-01-02T12:00:00Z').toISOString(),
        eligibleVoters: ['user-1'],
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Active vote session already exists for this admin in this league with overlapping dates');
    });

    it('should validate required fields', async () => {
      const result = await openUseCase.execute({
        voteSessionId: '',
        leagueId: '',
        adminId: '',
        startDate: '',
        endDate: '',
        eligibleVoters: [],
      });

      expect(result.success).toBe(false);
      expect(result.errors?.length).toBeGreaterThan(0);
    });

    it('should validate date order', async () => {
      const result = await openUseCase.execute({
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: tomorrow.toISOString(),
        endDate: now.toISOString(),
        eligibleVoters: ['user-1'],
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('startDate must be before endDate');
    });

    it('should validate duplicate eligible voters', async () => {
      const result = await openUseCase.execute({
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1', 'user-2', 'user-1'],
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Duplicate eligible voters are not allowed');
    });
  });

  describe('CastAdminVoteUseCase', () => {
    beforeEach(async () => {
      // Create a session for voting tests
      await openUseCase.execute({
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1', 'user-2', 'user-3'],
      });
    });

    it('should allow eligible voter to cast positive vote', async () => {
      const result = await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: true,
      });

      expect(result.success).toBe(true);
      expect(result.voterId).toBe('user-1');

      const session = await mockSessionRepo.findById('vote-123');
      expect(session!.votes.length).toBe(1);
      expect(session!.votes[0].voterId).toBe('user-1');
      expect(session!.votes[0].positive).toBe(true);
    });

    it('should allow eligible voter to cast negative vote', async () => {
      const result = await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: false,
      });

      expect(result.success).toBe(true);
      const session = await mockSessionRepo.findById('vote-123');
      expect(session!.votes[0].positive).toBe(false);
    });

    it('should reject non-eligible voter', async () => {
      const result = await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-999',
        positive: true,
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Voter user-999 is not eligible for this session');
    });

    it('should prevent duplicate votes', async () => {
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: true,
      });

      const result = await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: false,
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Voter user-1 has already voted');
    });

    it('should reject votes after session closes', async () => {
      // Close the session first
      await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      const result = await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-2',
        positive: true,
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Session is closed');
    });

    it('should reject votes outside voting window', async () => {
      // Create session in future
      const futureStart = new Date('2025-02-01T00:00:00Z');
      const futureEnd = new Date('2025-02-02T00:00:00Z');

      await openUseCase.execute({
        voteSessionId: 'vote-future',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: futureStart.toISOString(),
        endDate: futureEnd.toISOString(),
        eligibleVoters: ['user-1'],
      });

      const result = await castUseCase.execute({
        voteSessionId: 'vote-future',
        voterId: 'user-1',
        positive: true,
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Vote session is not open for voting');
    });
  });

  describe('CloseAdminVoteSessionUseCase', () => {
    beforeEach(async () => {
      // Create a session for closing tests
      await openUseCase.execute({
        voteSessionId: 'vote-123',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1', 'user-2', 'user-3', 'user-4'],
      });
    });

    it('should close session and create positive outcome events', async () => {
      // Cast votes
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: true,
      });
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-2',
        positive: true,
      });
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-3',
        positive: false,
      });

      const result = await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      expect(result.success).toBe(true);
      expect(result.outcome).toBeDefined();
      expect(result.outcome!.outcome).toBe('positive');
      expect(result.outcome!.percentPositive).toBe(66.67);
      expect(result.outcome!.count.total).toBe(3);
      expect(result.eventsCreated).toBe(1);

      // Verify session is closed
      const session = await mockSessionRepo.findById('vote-123');
      expect(session!.closed).toBe(true);
      expect(session!.outcome).toBeDefined();

      // Verify rating events were created
      const events = await mockEventRepo.findByUserId('admin-789');
      expect(events.length).toBe(1);
      expect(events[0].dimension.value).toBe('adminTrust');
      expect(events[0].reason.code).toBe('ADMIN_VOTE_OUTCOME_POSITIVE');
    });

    it('should create negative outcome events', async () => {
      // Cast mostly negative votes
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: false,
      });
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-2',
        positive: false,
      });
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-3',
        positive: true,
      });

      const result = await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      expect(result.success).toBe(true);
      expect(result.outcome!.outcome).toBe('negative');
      expect(result.outcome!.percentPositive).toBe(33.33);
      expect(result.eventsCreated).toBe(1);

      const events = await mockEventRepo.findByUserId('admin-789');
      expect(events[0].reason.code).toBe('ADMIN_VOTE_OUTCOME_NEGATIVE');
      expect(events[0].delta.value).toBeLessThan(0);
    });

    it('should handle tie outcome', async () => {
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: true,
      });
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-2',
        positive: false,
      });

      const result = await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      expect(result.success).toBe(true);
      expect(result.outcome!.outcome).toBe('tie');
      expect(result.eventsCreated).toBe(0); // No events for tie
    });

    it('should handle no votes', async () => {
      const result = await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      expect(result.success).toBe(true);
      expect(result.outcome!.outcome).toBe('tie');
      expect(result.outcome!.participationRate).toBe(0);
      expect(result.eventsCreated).toBe(0);
    });

    it('should reject closing already closed session', async () => {
      await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      const result = await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Vote session is already closed');
    });

    it('should reject non-owner trying to close', async () => {
      const result = await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'wrong-admin',
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Admin does not own this vote session');
    });

    it('should reject closing outside voting window', async () => {
      // Create session in future
      const futureStart = new Date('2025-02-01T00:00:00Z');
      const futureEnd = new Date('2025-02-02T00:00:00Z');

      await openUseCase.execute({
        voteSessionId: 'vote-future',
        leagueId: 'league-456',
        adminId: 'admin-789',
        startDate: futureStart.toISOString(),
        endDate: futureEnd.toISOString(),
        eligibleVoters: ['user-1'],
      });

      const result = await closeUseCase.execute({
        voteSessionId: 'vote-future',
        adminId: 'admin-789',
      });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Cannot close session outside the voting window');
    });

    it('should update admin rating snapshot', async () => {
      // Cast votes
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-1',
        positive: true,
      });
      await castUseCase.execute({
        voteSessionId: 'vote-123',
        voterId: 'user-2',
        positive: true,
      });

      await closeUseCase.execute({
        voteSessionId: 'vote-123',
        adminId: 'admin-789',
      });

      // Check snapshot was updated
      const snapshot = await mockRatingRepo.findByUserId('admin-789');
      expect(snapshot).toBeDefined();
      expect(snapshot!.adminTrust.value).toBeGreaterThan(50); // Should have increased
    });
  });

  describe('Integration: Full vote flow', () => {
    it('should complete full flow: open -> cast votes -> close -> events', async () => {
      // 1. Open session
      const openResult = await openUseCase.execute({
        voteSessionId: 'vote-full',
        leagueId: 'league-full',
        adminId: 'admin-full',
        startDate: now.toISOString(),
        endDate: tomorrow.toISOString(),
        eligibleVoters: ['user-1', 'user-2', 'user-3', 'user-4', 'user-5'],
      });
      expect(openResult.success).toBe(true);

      // 2. Cast votes
      const votes = [
        { voterId: 'user-1', positive: true },
        { voterId: 'user-2', positive: true },
        { voterId: 'user-3', positive: true },
        { voterId: 'user-4', positive: false },
      ];

      for (const vote of votes) {
        const castResult = await castUseCase.execute({
          voteSessionId: 'vote-full',
          voterId: vote.voterId,
          positive: vote.positive,
        });
        expect(castResult.success).toBe(true);
      }

      // 3. Close session
      const closeResult = await closeUseCase.execute({
        voteSessionId: 'vote-full',
        adminId: 'admin-full',
      });

      expect(closeResult.success).toBe(true);
      expect(closeResult.outcome).toEqual({
        percentPositive: 75,
        count: { positive: 3, negative: 1, total: 4 },
        eligibleVoterCount: 5,
        participationRate: 80,
        outcome: 'positive',
      });
      expect(closeResult.eventsCreated).toBe(1);

      // 4. Verify events in ledger
      const events = await mockEventRepo.findByUserId('admin-full');
      expect(events.length).toBe(1);
      
      const event = events[0];
      expect(event.userId).toBe('admin-full');
      expect(event.dimension.value).toBe('adminTrust');
      expect(event.source.type).toBe('vote');
      expect(event.source.id).toBe('vote-full');
      expect(event.reason.code).toBe('ADMIN_VOTE_OUTCOME_POSITIVE');
      expect(event.reason.summary).toContain('75% positive');
      expect(event.weight).toBe(4); // vote count

      // 5. Verify snapshot
      const snapshot = await mockRatingRepo.findByUserId('admin-full');
      expect(snapshot).toBeDefined();
      expect(snapshot!.adminTrust.value).toBeGreaterThan(50);
    });
  });
});