import { EventType } from '../../lib/types/agent';
import { logger } from '../../lib/logger';
import { emitEvent } from '../../lib/utils/bus';
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';
import { getDocClient } from '../../lib/utils/ddb-client';
import { Resource } from 'sst';
import { getReputation, computeReputationScore } from '../../lib/memory/reputation-operations';
import { BaseMemoryProvider } from '../../lib/memory/base';

interface ConsensusRequestDetail {
  requestId: string;
  proposal: string;
  initiatorId: string;
  participants: string[];
  mode?: 'majority' | 'unanimous' | 'weighted';
}

interface ConsensusVoteDetail {
  requestId: string;
  voterId: string;
  vote: boolean;
  reasoning?: string;
  weight?: number;
}

/**
 * Handles consensus requests and votes from the swarm.
 * Manages the state of active consensus cycles in DynamoDB.
 *
 * @param event - The event payload containing the consensus detail.
 * @param detailType - The event type string indicating whether this is a request or vote.
 */
export async function handleConsensus(
  event: { detail: ConsensusRequestDetail | ConsensusVoteDetail },
  detailType: string
): Promise<void> {
  const docClient = getDocClient();
  const resource = Resource as unknown as { MemoryTable: { name: string } };
  const tableName = resource.MemoryTable.name;

  try {
    if (detailType === EventType.CONSENSUS_REQUEST) {
      const {
        requestId,
        proposal,
        initiatorId,
        participants,
        mode = 'majority',
      } = event.detail as ConsensusRequestDetail;

      logger.info(`[Consensus] New request ${requestId} from ${initiatorId} (Mode: ${mode})`);

      await docClient.send(
        new UpdateCommand({
          TableName: tableName,
          Key: { userId: `CONSENSUS#${requestId}`, timestamp: 0 },
          UpdateExpression:
            'SET proposal = :prop, initiatorId = :init, participants = :parts, mode = :mode, votes = :empty_list, status = :status, createdAt = :now, expiresAt = :ttl',
          ExpressionAttributeValues: {
            ':prop': proposal,
            ':init': initiatorId,
            ':parts': participants,
            ':mode': mode,
            ':empty_list': [],
            ':status': 'PENDING',
            ':now': Date.now(),
            ':ttl': Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 7 days
          },
        })
      );
    } else if (detailType === EventType.CONSENSUS_VOTE) {
      const { requestId, voterId, vote, reasoning } = event.detail as ConsensusVoteDetail;

      logger.info(`[Consensus] Vote from ${voterId} for ${requestId}: ${vote ? 'YES' : 'NO'}`);

      // P0 Security Fix: Use GetCommand to validate voter is a participant
      const { GetCommand } = await import('@aws-sdk/lib-dynamodb');
      const getResponse = await docClient.send(
        new GetCommand({
          TableName: tableName,
          Key: { userId: `CONSENSUS#${requestId}`, timestamp: 0 },
        })
      );

      const currentState = getResponse.Item as
        | {
            participants: string[];
            votes: unknown[];
            mode: string;
            status: string;
            initiatorId: string;
          }
        | undefined;
      if (!currentState) {
        logger.warn(`[Consensus] Request ${requestId} not found`);
        return;
      }

      // Security: Check if voter is a valid participant
      const participants = currentState.participants || [];
      if (!participants.includes(voterId)) {
        logger.warn(
          `[Consensus] Rejected vote from non-participant ${voterId} for request ${requestId}`
        );
        return;
      }

      // Lookup reputation for weighted voting
      let weight = 1.0;
      try {
        const memBase = new BaseMemoryProvider();
        const reputation = await getReputation(memBase, voterId);
        if (reputation) {
          weight = computeReputationScore(reputation);
          logger.info(`[Consensus] Computed weight for ${voterId}: ${weight.toFixed(3)}`);
        } else {
          logger.info(`[Consensus] No reputation for ${voterId}, using default weight 1.0`);
        }
      } catch (err) {
        logger.warn(
          `[Consensus] Failed to lookup reputation for ${voterId}, defaulting to 1.0`,
          err
        );
      }

      const response = await docClient.send(
        new UpdateCommand({
          TableName: tableName,
          Key: { userId: `CONSENSUS#${requestId}`, timestamp: 0 },
          UpdateExpression: 'SET votes = list_append(if_not_exists(votes, :empty_list), :vote)',
          ExpressionAttributeValues: {
            ':vote': [{ voterId, vote, reasoning, timestamp: Date.now(), weight }],
            ':empty_list': [],
          },
          ReturnValues: 'ALL_NEW',
        })
      );

      const updatedState = response.Attributes;
      if (!updatedState) return;

      const totalVotes = (updatedState.votes as unknown[]).length;
      const requiredParticipants = participants.length;
      const yesVotes = (updatedState.votes as unknown[]).filter(
        (v: unknown) => (v as { vote: boolean }).vote
      ).length;

      let isReached = false;
      let finalResult = false;

      if (updatedState.mode === 'unanimous') {
        if (totalVotes === requiredParticipants) {
          isReached = true;
          finalResult = yesVotes === requiredParticipants;
        }
      } else if (updatedState.mode === 'weighted') {
        if (totalVotes === requiredParticipants) {
          isReached = true;
          let totalWeight = 0;
          let yesWeight = 0;
          for (const v of updatedState.votes as unknown[]) {
            const w = (v as { weight?: number }).weight ?? 1.0;
            totalWeight += w;
            if ((v as { vote: boolean }).vote) yesWeight += w;
          }
          finalResult = totalWeight > 0 && yesWeight / totalWeight > 0.5;
        }
      } else {
        if (totalVotes >= Math.ceil(requiredParticipants / 2)) {
          if (yesVotes > requiredParticipants / 2) {
            isReached = true;
            finalResult = true;
          } else if (totalVotes - yesVotes > requiredParticipants / 2) {
            isReached = true;
            finalResult = false;
          } else if (totalVotes === requiredParticipants) {
            isReached = true;
            finalResult = yesVotes > requiredParticipants / 2;
          }
        }
      }

      if (isReached && updatedState.status === 'PENDING') {
        logger.info(
          `[Consensus] Request ${requestId} finalized: ${finalResult ? 'APPROVED' : 'REJECTED'}`
        );

        await docClient.send(
          new UpdateCommand({
            TableName: tableName,
            Key: { userId: `CONSENSUS#${requestId}`, timestamp: 0 },
            UpdateExpression: 'SET status = :status, finalizedAt = :now, result = :result',
            ExpressionAttributeValues: {
              ':status': 'COMPLETED',
              ':now': Date.now(),
              ':result': finalResult,
            },
          })
        );

        await emitEvent('consensus-handler', EventType.CONSENSUS_REACHED, {
          requestId,
          result: finalResult,
          initiatorId: currentState.initiatorId,
          votes: updatedState.votes,
        });
      }
    }
  } catch (error) {
    logger.error('[Consensus] Error handling consensus event:', error);
    throw error;
  }
}
