/**
 * TeamProvisioningAgent — A2A pipeline for JarvisJr enterprise team onboarding.
 *
 * Agent roster (3 steps):
 *   1. NewMemberDetectorAgent  — list team members with 'invited' or recently joined status
 *   2. OnboardingAgent         — per new member: recommend starter agents, seed initial memory, schedule check-in
 *   3. ProvisioningReportAgent — assemble per-member onboarding action report
 *
 * MCP tools:
 *   jarvis.teams.provision(teamId, sinceHours?) — run pipeline for newly joined/invited members
 */

import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import type { McpToolRequest } from '../tools/types.js';
import {
  jarvisTeamsListMembers,
  jarvisAgentsList,
  jarvisMemoryCreate,
  type JarvisTeamMemberDoc,
  type JarvisAgentDoc,
} from '../../lib/jarvis-client.js';
import {
  trackRunCompleted,
  trackRunFailed,
  trackRunStarted,
  trackStepCompleted,
  trackStepFailed,
  trackStepStarted,
} from './run-tracker.js';
import { createSupportCaseForRun } from './governance.js';

// ── Types ──────────────────────────────────────────────────────────────────────

interface MemberOnboardingResult {
  userId: string;
  teamId: string;
  memberRole: string;
  status: string;
  recommendedAgents: string[];
  memorySeeded: boolean;
  checkInScheduled: boolean;
  errors: string[];
}

export interface TeamProvisioningReport {
  runId: string;
  productId: 'jarvisjr';
  teamId: string;
  totalMembers: number;
  newMembersProcessed: number;
  fullyOnboarded: number;
  partiallyOnboarded: number;
  failed: number;
  perMember: MemberOnboardingResult[];
  summary: string;
  generatedAt: string;
}

// ── Step 1: NewMemberDetectorAgent ─────────────────────────────────────────────

function isNewMember(member: JarvisTeamMemberDoc, sinceMs: number): boolean {
  if (member.status === 'invited') return true;
  if (member.status === 'active' && member.joinedAt) {
    const joinedAt = new Date(member.joinedAt).getTime();
    return Date.now() - joinedAt < sinceMs;
  }
  return false;
}

async function detectNewMembers(
  teamId: string,
  sinceMs: number,
  opts: { token?: string; requestId?: string }
): Promise<{ all: JarvisTeamMemberDoc[]; newMembers: JarvisTeamMemberDoc[] }> {
  try {
    const result = await jarvisTeamsListMembers(teamId, opts);
    const newMembers = result.members.filter(m => isNewMember(m, sinceMs));
    return { all: result.members, newMembers };
  } catch {
    return { all: [], newMembers: [] };
  }
}

// ── Step 2: OnboardingAgent ────────────────────────────────────────────────────

const STARTER_AGENT_ROLES: Record<string, string[]> = {
  owner: ['executive_coach', 'goal_setter', 'accountability_partner'],
  manager: ['leadership_coach', 'team_motivator'],
  member: ['productivity_coach', 'skill_builder'],
};

async function onboardMember(
  member: JarvisTeamMemberDoc,
  availableAgents: JarvisAgentDoc[],
  opts: { token?: string; requestId?: string }
): Promise<MemberOnboardingResult> {
  const errors: string[] = [];

  // Recommend agents matching the member's role-based preferences
  const preferredRoles = STARTER_AGENT_ROLES[member.role] ?? STARTER_AGENT_ROLES['member']!;
  const recommendedAgents = availableAgents
    .filter(a => {
      const role = (
        ((a as unknown as Record<string, unknown>)['role'] as string) ?? ''
      ).toLowerCase();
      return preferredRoles.some(r => role.includes(r.replace('_', '')));
    })
    .slice(0, 3)
    .map(a => a.id);

  // If no matching agents found, just recommend the first available agents
  const finalRecommended =
    recommendedAgents.length > 0 ? recommendedAgents : availableAgents.slice(0, 2).map(a => a.id);

  // Seed initial context memory for each recommended agent
  let memorySeeded = false;
  for (const agentId of finalRecommended) {
    try {
      await jarvisMemoryCreate(
        agentId,
        {
          sessionId: `team-provision-${member.teamId}`,
          type: 'context',
          content: `New team member: userId=${member.userId}, role=${member.role}, team=${member.teamId}. Joined: ${member.joinedAt}.`,
          importance: 0.6,
          tags: ['team-onboarding', member.role],
        },
        opts
      );
      memorySeeded = true;
    } catch (err) {
      errors.push(
        `Memory seed for agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`
      );
    }
  }

  // Check-in scheduling is indicated (actual scheduling requires a background job integration)
  // Here we mark it as scheduled if memory seeding succeeded
  const checkInScheduled = memorySeeded;

  return {
    userId: member.userId,
    teamId: member.teamId,
    memberRole: member.role,
    status: member.status,
    recommendedAgents: finalRecommended,
    memorySeeded,
    checkInScheduled,
    errors,
  };
}

// ── Step 3: ProvisioningReportAgent ───────────────────────────────────────────

function assembleProvisioningReport(
  runId: string,
  teamId: string,
  totalMembers: number,
  results: MemberOnboardingResult[]
): TeamProvisioningReport {
  const fullyOnboarded = results.filter(
    r => r.memorySeeded && r.checkInScheduled && r.errors.length === 0
  ).length;
  const failed = results.filter(r => !r.memorySeeded && r.errors.length > 0).length;
  const partiallyOnboarded = results.length - fullyOnboarded - failed;

  const summary =
    results.length === 0
      ? `No new/invited members found in the specified window for team ${teamId}.`
      : `Processed ${results.length} new member(s) for team ${teamId}. ${fullyOnboarded} fully onboarded, ${partiallyOnboarded} partial, ${failed} failed.`;

  return {
    runId,
    productId: 'jarvisjr',
    teamId,
    totalMembers,
    newMembersProcessed: results.length,
    fullyOnboarded,
    partiallyOnboarded,
    failed,
    perMember: results,
    summary,
    generatedAt: new Date().toISOString(),
  };
}

// ── Pipeline runner ────────────────────────────────────────────────────────────

export async function runTeamProvisioningPipeline(
  teamId: string,
  sinceHours: number,
  req: McpToolRequest
): Promise<TeamProvisioningReport> {
  const runId = randomUUID();
  const opts = { token: req.headers.authorization?.slice(7), requestId: req.id };
  const sinceMs = sinceHours * 60 * 60 * 1000;
  let currentStep: { stepName: 'detect' | 'onboard' | 'report'; order: number } | undefined;

  await safeTrack(() =>
    trackRunStarted({
      runId,
      productId: 'jarvisjr',
      name: 'team-provisioning',
      requestId: req.id,
      token: opts.token,
      input: { teamId, sinceHours },
    })
  );

  try {
    currentStep = { stepName: 'detect', order: 1 };
    const detectStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'jarvisjr',
        stepName: detectStep.stepName,
        order: detectStep.order,
        token: opts.token,
        requestId: req.id,
        input: { teamId, sinceHours },
      })
    );
    req.log.info({ runId, stepId: 'detect', teamId, sinceHours }, 'NewMemberDetectorAgent start');
    const { all, newMembers } = await detectNewMembers(teamId, sinceMs, opts);
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'jarvisjr',
        stepName: detectStep.stepName,
        order: detectStep.order,
        token: opts.token,
        requestId: req.id,
        output: { totalMembers: all.length, newMembers: newMembers.length },
      })
    );
    req.log.info(
      { runId, stepId: 'detect', total: all.length, newCount: newMembers.length },
      'NewMemberDetectorAgent done'
    );

    currentStep = { stepName: 'onboard', order: 2 };
    const onboardStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'jarvisjr',
        stepName: onboardStep.stepName,
        order: onboardStep.order,
        token: opts.token,
        requestId: req.id,
        input: { newMembers: newMembers.length },
      })
    );
    req.log.info(
      { runId, stepId: 'onboard', newCount: newMembers.length },
      'OnboardingAgent start'
    );
    const agentsResult = await jarvisAgentsList({ limit: 20 }, opts).catch(() => ({
      agents: [] as JarvisAgentDoc[],
      total: 0,
    }));
    const results: MemberOnboardingResult[] = [];
    for (const member of newMembers) {
      const result = await onboardMember(member, agentsResult.agents, opts);
      results.push(result);
    }
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'jarvisjr',
        stepName: onboardStep.stepName,
        order: onboardStep.order,
        token: opts.token,
        requestId: req.id,
        output: {
          seeded: results.filter(r => r.memorySeeded).length,
          failed: results.filter(r => r.errors.length > 0).length,
        },
      })
    );
    req.log.info(
      { runId, stepId: 'onboard', seeded: results.filter(r => r.memorySeeded).length },
      'OnboardingAgent done'
    );

    currentStep = { stepName: 'report', order: 3 };
    const reportStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'jarvisjr',
        stepName: reportStep.stepName,
        order: reportStep.order,
        token: opts.token,
        requestId: req.id,
      })
    );
    req.log.info({ runId, stepId: 'report' }, 'ProvisioningReportAgent start');
    const report = assembleProvisioningReport(runId, teamId, all.length, results);
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'jarvisjr',
        stepName: reportStep.stepName,
        order: reportStep.order,
        token: opts.token,
        requestId: req.id,
        output: {
          fullyOnboarded: report.fullyOnboarded,
          partiallyOnboarded: report.partiallyOnboarded,
          failed: report.failed,
        },
      })
    );
    await safeTrack(() =>
      trackRunCompleted({
        runId,
        productId: 'jarvisjr',
        name: 'team-provisioning',
        requestId: req.id,
        token: opts.token,
        output: {
          fullyOnboarded: report.fullyOnboarded,
          partiallyOnboarded: report.partiallyOnboarded,
          failed: report.failed,
        },
      })
    );
    if (report.failed > 0 || report.partiallyOnboarded > 0) {
      await createSupportCaseForRun(
        {
          productId: 'jarvisjr',
          runId,
          title: `Team provisioning follow-up for ${teamId}`,
          description: report.summary,
          priority: report.failed > 0 ? 'high' : 'medium',
          tags: ['a2a', 'team-provisioning', 'onboarding'],
        },
        opts
      );
    }
    req.log.info(
      { runId, stepId: 'report', summary: report.summary },
      'ProvisioningReportAgent done'
    );

    return report;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    if (currentStep) {
      const failedStep = currentStep;
      await safeTrack(() =>
        trackStepFailed({
          runId,
          productId: 'jarvisjr',
          stepName: failedStep.stepName,
          order: failedStep.order,
          token: opts.token,
          requestId: req.id,
          error: message,
        })
      );
    }
    await safeTrack(() =>
      trackRunFailed({
        runId,
        productId: 'jarvisjr',
        name: 'team-provisioning',
        requestId: req.id,
        token: opts.token,
        error: message,
      })
    );
    throw error;
  }
}

async function safeTrack(fn: () => Promise<void>): Promise<void> {
  try {
    await fn();
  } catch {
    // Tracking must never fail the pipeline itself.
  }
}

// ── MCP tool registration ─────────────────────────────────────────────────────

registerTool({
  name: 'jarvis.teams.provision',
  description:
    'A2A pipeline: detects new/invited JarvisJr enterprise team members, recommends starter coaching agents based on their role, seeds initial context memories in each agent, and marks check-in as scheduled. Returns a per-member onboarding action report. Requires admin role.',
  requiredRole: 'admin',
  inputSchema: z.object({
    teamId: z.string().min(1).describe('Enterprise team ID to provision'),
    sinceHours: z.coerce
      .number()
      .int()
      .min(1)
      .default(48)
      .describe('Look back this many hours for newly joined members (default 48h)'),
  }),
  async execute(args, req) {
    return runTeamProvisioningPipeline(args.teamId, args.sinceHours, req);
  },
});
