/**
 * MCP Tool: assess_mep_influence
 *
 * Compute MEP influence score from voting activity, committee roles,
 * rapporteurships, questions filed, and coalition building metrics.
 *
 * **Intelligence Perspective:** Core OSINT scorecard tool computing composite MEP
 * influence scores using CIA Political Scorecards methodology—enables comparative
 * ranking, trend analysis, and political weight assessment across 5 dimensions.
 *
 * ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
 */

import { AssessMepInfluenceSchema } from '../schemas/europeanParliament.js';
import { epClient } from '../clients/europeanParliamentClient.js';
import { auditLogger, toErrorMessage } from '../utils/auditLogger.js';
import { computeMepVotingActivityFromDoceo } from '../utils/doceoMepAggregator.js';
import { buildToolResponse } from './shared/responseBuilder.js';
import type { ToolResult } from './shared/types.js';

/**
 * Data source attribution surfaced in the response envelope so consumers
 * know whether the voting dimensions were sourced from the EP Open Data
 * API placeholder, the DOCEO RCV XML enrichment, or both.
 */
export type AssessMepInfluenceDataSource = 'EP_API' | 'DOCEO' | 'EP_API+DOCEO';

/**
 * Dimension weight configuration for influence scoring
 * Based on CIA Political Scorecards methodology from FUTURE_ARCHITECTURE.md
 */
const DIMENSION_WEIGHTS = {
  votingActivity: 0.25,
  legislativeOutput: 0.25,
  committeeEngagement: 0.20,
  parliamentaryOversight: 0.15,
  coalitionBuilding: 0.15
} as const;

/**
 * Influence dimension score
 */
interface DimensionScore {
  dimension: string;
  score: number;
  weight: number;
  weightedScore: number;
  metrics: Record<string, number>;
}

/**
 * MEP influence assessment result
 */
interface MepInfluenceAssessment {
  mepId: string;
  mepName: string;
  country: string;
  politicalGroup: string;
  period: { from: string; to: string };
  overallScore: number;
  rank: string;
  dimensions: DimensionScore[];
  computedAttributes: {
    participationRate: number;
    loyaltyScore: number;
    diversityIndex: number;
    effectivenessRatio: number;
    leadershipIndicator: number;
  };
  votingDataAvailable: boolean;
  confidenceLevel: 'HIGH' | 'MEDIUM' | 'LOW';
  dataFreshness: string;
  sourceAttribution: string;
  /**
   * Indicates which underlying data source(s) supplied the voting and
   * coalition-building dimensions.
   * - `EP_API`: only the EP Open Data API `MEPDetails.votingStatistics`
   *   (placeholder; typically zeros).
   * - `DOCEO`: per-MEP roll-call vote aggregation from DOCEO XML.
   * - `EP_API+DOCEO`: DOCEO supplied the RCV counts, EP API filled in
   *   fields DOCEO does not expose (e.g. attendance over a longer window).
   */
  dataSource: AssessMepInfluenceDataSource;
  methodology: string;
  dataQualityWarnings: string[];
}

/**
 * Compute voting activity score (0-100)
 */
function computeVotingActivityScore(stats: { totalVotes: number; attendanceRate: number }): {
  score: number;
  metrics: Record<string, number>;
} {
  const attendanceScore = stats.attendanceRate;
  const participationVolume = Math.min(100, (stats.totalVotes / 1500) * 100);
  const score = attendanceScore * 0.6 + participationVolume * 0.4;
  return {
    score: Math.round(score * 100) / 100,
    metrics: {
      attendanceRate: stats.attendanceRate,
      totalVotes: stats.totalVotes,
      participationVolume: Math.round(participationVolume * 100) / 100
    }
  };
}

/**
 * Compute legislative output score (0-100)
 */
function computeLegislativeOutputScore(roles: string[], committees: string[]): {
  score: number;
  metrics: Record<string, number>;
} {
  const rapporteurships = roles.filter(r => r.toLowerCase().includes('rapporteur')).length;
  const committeeRoles = roles.filter(r =>
    r.toLowerCase().includes('chair') || r.toLowerCase().includes('vice')
  ).length;
  const roleScore = Math.min(100, rapporteurships * 15 + committeeRoles * 20);
  const committeeDiversity = Math.min(100, committees.length * 20);
  const score = roleScore * 0.6 + committeeDiversity * 0.4;
  return {
    score: Math.round(score * 100) / 100,
    metrics: {
      rapporteurships,
      committeeRoles,
      totalCommittees: committees.length
    }
  };
}

/**
 * Compute committee engagement score (0-100)
 */
function computeCommitteeEngagementScore(committees: string[], roles: string[]): {
  score: number;
  metrics: Record<string, number>;
} {
  const leadershipRoles = roles.filter(r =>
    r.toLowerCase().includes('chair') ||
    r.toLowerCase().includes('coordinator') ||
    r.toLowerCase().includes('vice')
  ).length;
  const membershipBreadth = Math.min(100, committees.length * 25);
  const leadershipScore = Math.min(100, leadershipRoles * 30);
  const score = membershipBreadth * 0.5 + leadershipScore * 0.5;
  return {
    score: Math.round(score * 100) / 100,
    metrics: {
      committeeMemberships: committees.length,
      leadershipRoles,
      membershipBreadth: Math.round(membershipBreadth * 100) / 100
    }
  };
}

/**
 * Compute parliamentary oversight score (0-100) using real question data
 */
function computeOversightScore(questionCount: number): {
  score: number;
  metrics: Record<string, number>;
} {
  const questionVolume = Math.min(100, questionCount * 2);
  const topicDiversity = Math.min(100, questionCount * 10);
  const score = questionVolume * 0.5 + topicDiversity * 0.5;
  return {
    score: Math.round(score * 100) / 100,
    metrics: {
      questionsFound: questionCount,
      questionVolume: Math.round(questionVolume * 100) / 100,
      topicDiversity: Math.round(topicDiversity * 100) / 100
    }
  };
}

/**
 * Compute coalition building score (0-100)
 */
function computeCoalitionScore(stats: { votesFor: number; votesAgainst: number; abstentions: number; totalVotes: number }): {
  score: number;
  metrics: Record<string, number>;
} {
  const totalDecisive = stats.votesFor + stats.votesAgainst;
  const crossPartyRate = totalDecisive > 0
    ? Math.min(100, (stats.votesAgainst / totalDecisive) * 100 * 2)
    : 0;
  const engagementRate = stats.totalVotes > 0
    ? ((stats.totalVotes - stats.abstentions) / stats.totalVotes) * 100
    : 0;
  const score = crossPartyRate * 0.4 + engagementRate * 0.6;
  return {
    score: Math.round(score * 100) / 100,
    metrics: {
      crossPartyRate: Math.round(crossPartyRate * 100) / 100,
      engagementRate: Math.round(engagementRate * 100) / 100,
      decisiveVotes: totalDecisive
    }
  };
}

/**
 * Determine influence rank label
 */
function getRankLabel(score: number): string {
  if (score >= 80) return 'Very High Influence';
  if (score >= 60) return 'High Influence';
  if (score >= 40) return 'Moderate Influence';
  if (score >= 20) return 'Low Influence';
  return 'Minimal Influence';
}

/**
 * Input for dimension building
 */
interface DimensionInputs {
  votingDim: { score: number; metrics: Record<string, number> };
  legislativeDim: { score: number; metrics: Record<string, number> };
  committeeDim: { score: number; metrics: Record<string, number> };
  oversightDim: { score: number; metrics: Record<string, number> };
  coalitionDim: { score: number; metrics: Record<string, number> };
}

/**
 * Build dimensions array from computed scores
 */
function buildDimensions(inputs: DimensionInputs, includeMetrics: boolean): DimensionScore[] {
  const raw: { name: string; result: { score: number; metrics: Record<string, number> }; weight: number }[] = [
    { name: 'Voting Activity', result: inputs.votingDim, weight: DIMENSION_WEIGHTS.votingActivity },
    { name: 'Legislative Output', result: inputs.legislativeDim, weight: DIMENSION_WEIGHTS.legislativeOutput },
    { name: 'Committee Engagement', result: inputs.committeeDim, weight: DIMENSION_WEIGHTS.committeeEngagement },
    { name: 'Parliamentary Oversight', result: inputs.oversightDim, weight: DIMENSION_WEIGHTS.parliamentaryOversight },
    { name: 'Coalition Building', result: inputs.coalitionDim, weight: DIMENSION_WEIGHTS.coalitionBuilding }
  ];

  return raw.map(d => ({
    dimension: d.name,
    score: d.result.score,
    weight: d.weight,
    weightedScore: Math.round(d.result.score * d.weight * 100) / 100,
    metrics: includeMetrics ? d.result.metrics : {}
  }));
}

/**
 * Determine confidence level from real DOCEO RCV votes observed and
 * the legacy EP-API vote count. `HIGH` is reserved for paths backed
 * by at least one real DOCEO RCV observation; otherwise the score is
 * still placeholder-derived and stays at `LOW`/`MEDIUM` per the
 * EP-API vote count.
 */
function getConfidenceLevel(doceoRcvVotes: number, epApiTotalVotes: number): 'HIGH' | 'MEDIUM' | 'LOW' {
  if (doceoRcvVotes > 0) return 'HIGH';
  if (epApiTotalVotes > 100) return 'MEDIUM';
  return 'LOW';
}

/**
 * Collect data quality warnings based on available data
 */
function collectDataQualityWarnings(
  votingDataAvailable: boolean,
  questionCount: number,
  dataSource: AssessMepInfluenceDataSource,
  doceoFallback: boolean
): string[] {
  const warnings: string[] = [];
  if (doceoFallback) {
    warnings.push(
      'DOCEO RCV enrichment unavailable (timeout, network error, or no votes in the selected period) — voting dimensions fall back to EP API placeholder data'
    );
  }
  if (!votingDataAvailable && dataSource === 'EP_API') {
    warnings.push('Voting statistics unavailable from EP API — voting-based metrics (loyalty, participation, coalition building) set to zero');
  }
  if (questionCount === 0) {
    warnings.push('Parliamentary questions data unavailable or MEP has no questions — oversight dimension reports zero');
  }
  return warnings;
}

function buildDataFreshness(votingDataAvailable: boolean, dataSource: AssessMepInfluenceDataSource): string {
  const doceoNote = dataSource === 'EP_API'
    ? ''
    : ' Per-MEP voting activity and loyalty score derived from DOCEO RCV XML (near-real-time roll-call data).';
  return votingDataAvailable
    ? `Real-time EP API data — MEP voting statistics and committee memberships.${doceoNote}`
    : `Real-time EP API data — committee memberships only; voting statistics unavailable from EP API.${doceoNote}`;
}

function buildMethodology(votingDataAvailable: boolean, dataSource: AssessMepInfluenceDataSource): string {
  const portal = 'Data source: European Parliament Open Data Portal';
  const portalSuffix = dataSource === 'EP_API' ? '.' : ' + DOCEO XML.';
  const dimensionsSentence = dataSource === 'EP_API'
    ? 'Voting Activity and Coalition Building dimensions sourced from EP API MEPDetails.votingStatistics.'
    : 'Voting Activity and Coalition Building dimensions sourced from DOCEO RCV XML (per-MEP roll-call aggregation).';
  const questions = 'Parliamentary questions fetched from /parliamentary-questions endpoint.';

  if (votingDataAvailable) {
    return `CIA Political Scorecards - 5-dimension weighted scoring model using real EP Open Data. ${questions} ${dimensionsSentence} ${portal}${portalSuffix}`;
  }
  const fallbackSentence = dataSource === 'EP_API'
    ? 'Voting statistics unavailable — voting and participation dimensions use zero-based proxies.'
    : dimensionsSentence;
  return `CIA Political Scorecards - 5-dimension weighted scoring model. ${fallbackSentence} ${questions} ${portal}${portalSuffix}`;
}

/**
 * Build dataFreshness and methodology strings based on whether voting data is available.
 * When voting stats are unavailable, the text accurately reflects that voting-based
 * dimensions use zero-based proxies rather than live data.
 */
function buildInfluenceMetadata(
  votingDataAvailable: boolean,
  dataSource: AssessMepInfluenceDataSource
): { dataFreshness: string; methodology: string } {
  return {
    dataFreshness: buildDataFreshness(votingDataAvailable, dataSource),
    methodology: buildMethodology(votingDataAvailable, dataSource),
  };
}

/**
 * Handles the assess_mep_influence MCP tool request.
 *
 * Assesses an MEP's influence within the European Parliament by evaluating their
 * voting activity, parliamentary questions, committee leadership roles, and
 * seniority. Produces a multi-dimensional influence score with network centrality
 * and impact rank computations.
 *
 * @param args - Raw tool arguments, validated against {@link AssessMepInfluenceSchema}
 * @returns MCP tool result containing the MEP's influence scores, voting statistics,
 *   committee roles, question count, seniority metrics, and computed influence rank
 * @throws - If `args` fails schema validation (e.g., missing required fields or invalid format)
 * - If the European Parliament API is unreachable or returns an error response
 *
 * @example
 * ```typescript
 * const result = await handleAssessMepInfluence({
 *   mepId: '124810',
 *   includeVoting: true,
 *   includeCommittees: true
 * });
 * // Returns influence assessment with overall score, voting discipline,
 * // committee leadership, and seniority breakdown
 * ```
 *
 * @security - Input is validated with Zod before any API call.
 * - Personal data in responses is minimised per GDPR Article 5(1)(c).
 * - All requests are rate-limited and audit-logged per ISMS Policy AU-002.
 * @since 0.8.0
 * @see {@link assessMepInfluenceToolMetadata} for MCP schema registration
 * @see {@link handleTrackMepAttendance} for MEP attendance and participation tracking
 * Assess MEP influence tool handler
 *
 * Computes a composite influence scorecard for a single MEP using a
 * 5-dimension weighted model aligned with CIA Political Scorecards methodology.
 * Fetches live MEP profile and parliamentary questions from the EP Open Data API
 * to populate the scoring dimensions.
 *
 * **Dimensions (weighted):**
 * - Voting Activity (25%) — attendance rate + participation volume
 * - Legislative Output (25%) — rapporteurships + committee leadership roles
 * - Committee Engagement (20%) — membership breadth + leadership positions
 * - Parliamentary Oversight (15%) — parliamentary questions filed
 * - Coalition Building (15%) — cross-party voting rate + engagement rate
 *
 * @param args - Tool arguments matching AssessMepInfluenceSchema
 * @param args.mepId - MEP identifier (required)
 * @param args.dateFrom - Analysis start date in YYYY-MM-DD format (optional)
 * @param args.dateTo - Analysis end date in YYYY-MM-DD format (optional)
 * @param args.includeDetails - When true, includes per-dimension metric breakdown (optional)
 * @returns MCP ToolResult containing the `MepInfluenceAssessment` object as JSON
 * @throws {Error} When MEP is not found or the EP API request fails
 * @throws {ZodError} When input fails schema validation (missing mepId, invalid date format)
 *
 * @example
 * ```typescript
 * // Basic influence assessment
 * const result = await handleAssessMepInfluence({ mepId: "197558" });
 * const assessment = JSON.parse(result.content[0].text);
 * console.log(`${assessment.mepName}: ${assessment.rank} (${assessment.overallScore}/100)`);
 * ```
 *
 * @example
 * ```typescript
 * // Detailed assessment with dimension breakdown
 * const result = await handleAssessMepInfluence({
 *   mepId: "197558",
 *   dateFrom: "2024-01-01",
 *   dateTo: "2024-12-31",
 *   includeDetails: true
 * });
 * ```
 *
 * @security Input validated by Zod. Errors sanitized (no stack traces exposed).
 * Personal data (MEP profiles) access logged per GDPR Article 30.
 * ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
 */
interface VotingStats {
  totalVotes: number;
  votesFor: number;
  votesAgainst: number;
  abstentions: number;
  attendanceRate: number;
}

interface MergedVotingInputs {
  stats: VotingStats;
  dataSource: AssessMepInfluenceDataSource;
  doceoRcvVotes: number;
  loyaltyScoreFromDoceo: number | null;
  doceoFallback: boolean;
}

/**
 * Merge the DOCEO aggregator result with the EP API placeholder statistics.
 * DOCEO supplies RCV counts; EP API supplies attendanceRate when DOCEO has
 * no inspected votes.
 */
function mergeVotingStats(
  epApiStats: VotingStats,
  doceoResult: Awaited<ReturnType<typeof computeMepVotingActivityFromDoceo>>
): MergedVotingInputs {
  const doceoFallback = doceoResult === null;
  const doceoUsable = doceoResult !== null && doceoResult.stats.totalVotes > 0;
  if (!doceoUsable) {
    return {
      stats: epApiStats,
      dataSource: 'EP_API',
      doceoRcvVotes: doceoResult?.stats.totalVotes ?? 0,
      loyaltyScoreFromDoceo: null,
      doceoFallback,
    };
  }
  const attendanceRate = doceoResult.stats.attendanceRate > 0
    ? doceoResult.stats.attendanceRate
    : epApiStats.attendanceRate;
  return {
    stats: {
      totalVotes: doceoResult.stats.totalVotes,
      votesFor: doceoResult.stats.votesFor,
      votesAgainst: doceoResult.stats.votesAgainst,
      abstentions: doceoResult.stats.abstentions,
      attendanceRate,
    },
    dataSource: epApiStats.totalVotes > 0 ? 'EP_API+DOCEO' : 'DOCEO',
    doceoRcvVotes: doceoResult.stats.totalVotes,
    loyaltyScoreFromDoceo: doceoResult.stats.loyaltyScore,
    doceoFallback: false,
  };
}

function computeLoyaltyScore(stats: VotingStats, loyaltyFromDoceo: number | null): number {
  if (loyaltyFromDoceo !== null) return loyaltyFromDoceo;
  const totalDecisive = stats.votesFor + stats.votesAgainst;
  if (totalDecisive === 0) return 0;
  return Math.round((stats.votesFor / totalDecisive) * 100 * 100) / 100;
}

function buildSourceAttribution(dataSource: AssessMepInfluenceDataSource): string {
  if (dataSource === 'EP_API') {
    return 'European Parliament Open Data Portal - data.europarl.europa.eu';
  }
  return 'European Parliament Open Data Portal - data.europarl.europa.eu + DOCEO XML - europarl.europa.eu/doceo';
}

async function fetchQuestionCount(mepId: string): Promise<number> {
  try {
    const questions = await epClient.getParliamentaryQuestions({ author: mepId, limit: 100 });
    return questions.data.length;
  } catch (error: unknown) {
    auditLogger.logError('assess_mep_influence.fetch_questions', { mepId }, toErrorMessage(error));
    return 0;
  }
}

export async function handleAssessMepInfluence(
  args: unknown
): Promise<ToolResult> {
  const params = AssessMepInfluenceSchema.parse(args);

  try {
    const mep = await epClient.getMEPDetails(params.mepId);
    const epApiStats: VotingStats = mep.votingStatistics ?? {
      totalVotes: 0, votesFor: 0, votesAgainst: 0, abstentions: 0, attendanceRate: 0
    };

    // Try DOCEO RCV enrichment first (bounded, cached). Falls back to EP API on failure.
    const doceoResult = await computeMepVotingActivityFromDoceo(params.mepId, {
      dateFrom: params.dateFrom,
      dateTo: params.dateTo,
      politicalGroup: mep.politicalGroup,
    });
    const merged = mergeVotingStats(epApiStats, doceoResult);
    const { stats, dataSource, doceoRcvVotes, loyaltyScoreFromDoceo, doceoFallback } = merged;
    const votingDataAvailable = stats.totalVotes > 0;

    const questionCount = await fetchQuestionCount(params.mepId);

    const dataQualityWarnings = collectDataQualityWarnings(
      votingDataAvailable, questionCount, dataSource, doceoFallback
    );
    const { dataFreshness, methodology } = buildInfluenceMetadata(votingDataAvailable, dataSource);

    const votingDim = computeVotingActivityScore(stats);
    const legislativeDim = computeLegislativeOutputScore(mep.roles ?? [], mep.committees);
    const committeeDim = computeCommitteeEngagementScore(mep.committees, mep.roles ?? []);
    const oversightDim = computeOversightScore(questionCount);
    const coalitionDim = computeCoalitionScore(stats);

    const dimensions = buildDimensions(
      { votingDim, legislativeDim, committeeDim, oversightDim, coalitionDim },
      params.includeDetails
    );

    const overallScore = Math.round(
      dimensions.reduce((sum, d) => sum + d.weightedScore, 0) * 100
    ) / 100;

    const loyaltyScore = computeLoyaltyScore(stats, loyaltyScoreFromDoceo);

    const assessment: MepInfluenceAssessment = {
      mepId: params.mepId,
      mepName: mep.name,
      country: mep.country,
      politicalGroup: mep.politicalGroup,
      period: {
        from: params.dateFrom ?? '2024-01-01',
        to: params.dateTo ?? '2024-12-31'
      },
      overallScore,
      rank: getRankLabel(overallScore),
      dimensions,
      computedAttributes: {
        participationRate: stats.attendanceRate,
        loyaltyScore,
        diversityIndex: Math.min(100, Math.max(0, Math.round((mep.committees.length / 5) * 100 * 100) / 100)),
        effectivenessRatio: Math.round((votingDim.score + legislativeDim.score) / 2 * 100) / 100,
        leadershipIndicator: committeeDim.score
      },
      confidenceLevel: getConfidenceLevel(doceoRcvVotes, epApiStats.totalVotes),
      votingDataAvailable,
      dataFreshness,
      sourceAttribution: buildSourceAttribution(dataSource),
      dataSource,
      methodology,
      dataQualityWarnings,
    };

    return buildToolResponse(assessment);
  } catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new Error(`Failed to assess MEP influence: ${errorMessage}`);
  }
}

/**
 * Tool metadata for MCP registration
 */
export const assessMepInfluenceToolMetadata = {
  name: 'assess_mep_influence',
  description: 'Compute MEP influence score using a 5-dimension weighted model: Voting Activity (25%), Legislative Output (25%), Committee Engagement (20%), Parliamentary Oversight (15%), Coalition Building (15%). Returns overall score, rank, dimension breakdowns, and computed attributes including participation rate, loyalty score, diversity index, and leadership indicator.',
  inputSchema: {
    type: 'object' as const,
    properties: {
      mepId: {
        type: 'string',
        description: 'MEP identifier',
        minLength: 1,
        maxLength: 100
      },
      dateFrom: {
        type: 'string',
        description: 'Analysis start date (YYYY-MM-DD format)',
        pattern: '^\\d{4}-\\d{2}-\\d{2}$'
      },
      dateTo: {
        type: 'string',
        description: 'Analysis end date (YYYY-MM-DD format)',
        pattern: '^\\d{4}-\\d{2}-\\d{2}$'
      },
      includeDetails: {
        type: 'boolean',
        description: 'Include detailed breakdown per dimension',
        default: false
      }
    },
    required: ['mepId']
  }
};
