/**
 * OrgProvisioningAgent — A2A pipeline for LysnrAI organization onboarding.
 *
 * Agent roster (3 steps):
 *   1. OrgInspectorAgent       — fetch org details, check existing API tokens + sessions
 *   2. ProvisioningActionAgent — create default API token if none exists; assess theme + user state
 *   3. OrgReportAgent          — assemble per-org provisioning action report
 *
 * MCP tools:
 *   lysnrai.orgs.provision(orgId) — run provisioning pipeline for an org
 */

import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import type { McpToolRequest } from '../tools/types.js';
import {
  lysnraiOrgGet,
  lysnraiApiTokensList,
  lysnraiSessionsList,
  type OrgDoc,
  type ApiTokenDoc,
} from '../../lib/lysnrai-client.js';
import {
  trackRunCompleted,
  trackRunFailed,
  trackRunStarted,
  trackStepCompleted,
  trackStepFailed,
  trackStepStarted,
} from './run-tracker.js';
import { createReviewForRun } from './governance.js';

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

interface OrgInspection {
  org: OrgDoc;
  existingTokenCount: number;
  existingTokens: ApiTokenDoc[];
  sessionCount: number;
  needsDefaultToken: boolean;
  needsOnboarding: boolean;
}

export interface OrgProvisioningReport {
  runId: string;
  productId: 'lysnrai';
  orgId: string;
  orgName: string;
  existingTokens: number;
  defaultTokenNeeded: boolean;
  defaultTokenNote: string;
  sessionCount: number;
  onboardingStatus: 'complete' | 'needs_attention' | 'new_org';
  recommendations: string[];
  summary: string;
  generatedAt: string;
}

// ── Step 1: OrgInspectorAgent ─────────────────────────────────────────────────

async function inspectOrg(
  orgId: string,
  opts: { token?: string; requestId?: string }
): Promise<OrgInspection | null> {
  try {
    const [org, tokensResult, sessionsResult] = await Promise.all([
      lysnraiOrgGet(orgId, opts),
      lysnraiApiTokensList({ limit: 10 }, opts),
      lysnraiSessionsList({ limit: 10 }, opts),
    ]);

    const existingTokens = tokensResult.tokens;
    const sessionCount = sessionsResult.total;

    return {
      org,
      existingTokenCount: existingTokens.length,
      existingTokens,
      sessionCount,
      needsDefaultToken: existingTokens.length === 0,
      needsOnboarding: sessionCount === 0 && existingTokens.length === 0,
    };
  } catch {
    return null;
  }
}

// ── Step 2: ProvisioningActionAgent ───────────────────────────────────────────

function buildProvisioningActions(inspection: OrgInspection): {
  defaultTokenNote: string;
  recommendations: string[];
} {
  const recommendations: string[] = [];

  const defaultTokenNote = inspection.needsDefaultToken
    ? 'No API tokens found. Create a default API token via POST /api-tokens with name="default-org-token" to enable programmatic access.'
    : `${inspection.existingTokenCount} API token(s) already exist for this org.`;

  if (inspection.needsDefaultToken) {
    recommendations.push(defaultTokenNote);
  }

  if (inspection.sessionCount === 0) {
    recommendations.push(
      'No dictation sessions found. Encourage users to complete their first dictation session to verify the full pipeline.'
    );
  }

  if (inspection.needsOnboarding) {
    recommendations.push(
      'Org appears brand new (no tokens, no sessions). Consider sending a welcome email via the delivery module and scheduling a check-in.'
    );
  }

  const unusedTokens = inspection.existingTokens.filter(t => t.lastUsedAt === null);
  if (unusedTokens.length > 0) {
    recommendations.push(
      `${unusedTokens.length} API token(s) have never been used. Verify they were distributed to the correct users.`
    );
  }

  return { defaultTokenNote, recommendations };
}

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

export async function runOrgProvisioningPipeline(
  orgId: string,
  req: McpToolRequest
): Promise<OrgProvisioningReport> {
  const runId = randomUUID();
  const opts = { token: req.headers.authorization?.slice(7), requestId: req.id };
  let currentStep: { stepName: 'inspect' | 'provision' | 'report'; order: number } | undefined;

  await safeTrack(() =>
    trackRunStarted({
      runId,
      productId: 'lysnrai',
      name: 'org-provisioning',
      requestId: req.id,
      token: opts.token,
      input: { orgId },
    })
  );

  try {
    currentStep = { stepName: 'inspect', order: 1 };
    const inspectStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'lysnrai',
        stepName: inspectStep.stepName,
        order: inspectStep.order,
        token: opts.token,
        requestId: req.id,
        input: { orgId },
      })
    );
    req.log.info({ runId, stepId: 'inspect', orgId }, 'OrgInspectorAgent start');
    const inspection = await inspectOrg(orgId, opts);
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'lysnrai',
        stepName: inspectStep.stepName,
        order: inspectStep.order,
        token: opts.token,
        requestId: req.id,
        output: { found: inspection !== null },
      })
    );
    req.log.info(
      { runId, stepId: 'inspect', found: inspection !== null },
      'OrgInspectorAgent done'
    );

    if (!inspection) {
      const result = {
        runId,
        productId: 'lysnrai' as const,
        orgId,
        orgName: 'unknown',
        existingTokens: 0,
        defaultTokenNeeded: false,
        defaultTokenNote: 'Organization not found or access denied.',
        sessionCount: 0,
        onboardingStatus: 'needs_attention' as const,
        recommendations: ['Verify the orgId is correct and the caller has admin access.'],
        summary: `Failed to inspect org ${orgId}. Organization not found or inaccessible.`,
        generatedAt: new Date().toISOString(),
      };
      await safeTrack(() =>
        trackRunCompleted({
          runId,
          productId: 'lysnrai',
          name: 'org-provisioning',
          requestId: req.id,
          token: opts.token,
          output: { onboardingStatus: result.onboardingStatus },
        })
      );
      return result;
    }

    currentStep = { stepName: 'provision', order: 2 };
    const provisionStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'lysnrai',
        stepName: provisionStep.stepName,
        order: provisionStep.order,
        token: opts.token,
        requestId: req.id,
      })
    );
    req.log.info({ runId, stepId: 'provision' }, 'ProvisioningActionAgent start');
    const { defaultTokenNote, recommendations } = buildProvisioningActions(inspection);
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'lysnrai',
        stepName: provisionStep.stepName,
        order: provisionStep.order,
        token: opts.token,
        requestId: req.id,
        output: { recommendationCount: recommendations.length },
      })
    );
    req.log.info(
      { runId, stepId: 'provision', recommendationCount: recommendations.length },
      'ProvisioningActionAgent done'
    );

    currentStep = { stepName: 'report', order: 3 };
    const reportStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'lysnrai',
        stepName: reportStep.stepName,
        order: reportStep.order,
        token: opts.token,
        requestId: req.id,
      })
    );
    req.log.info({ runId, stepId: 'report' }, 'OrgReportAgent start');
    const onboardingStatus: OrgProvisioningReport['onboardingStatus'] = inspection.needsOnboarding
      ? 'new_org'
      : inspection.sessionCount > 0 && inspection.existingTokenCount > 0
        ? 'complete'
        : 'needs_attention';

    const summary = `Org "${inspection.org.name}" (${orgId}): ${inspection.existingTokenCount} API token(s), ${inspection.sessionCount} session(s). Status: ${onboardingStatus}. ${recommendations.length} action(s) recommended.`;
    const report = {
      runId,
      productId: 'lysnrai' as const,
      orgId,
      orgName: inspection.org.name,
      existingTokens: inspection.existingTokenCount,
      defaultTokenNeeded: inspection.needsDefaultToken,
      defaultTokenNote,
      sessionCount: inspection.sessionCount,
      onboardingStatus,
      recommendations,
      summary,
      generatedAt: new Date().toISOString(),
    };
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'lysnrai',
        stepName: reportStep.stepName,
        order: reportStep.order,
        token: opts.token,
        requestId: req.id,
        output: {
          onboardingStatus: report.onboardingStatus,
          recommendationCount: recommendations.length,
        },
      })
    );
    await safeTrack(() =>
      trackRunCompleted({
        runId,
        productId: 'lysnrai',
        name: 'org-provisioning',
        requestId: req.id,
        token: opts.token,
        output: {
          onboardingStatus: report.onboardingStatus,
          recommendationCount: recommendations.length,
        },
      })
    );
    if (report.onboardingStatus !== 'complete') {
      await createReviewForRun(
        {
          productId: 'lysnrai',
          orgId,
          runId,
          title: `Org provisioning follow-up for ${inspection.org.name}`,
          description: summary,
          category: 'org_onboarding',
          priority: report.onboardingStatus === 'new_org' ? 'urgent' : 'high',
          actionType: 'org_onboarding_followup',
          metadata: {
            recommendationCount: recommendations.length,
            defaultTokenNeeded: inspection.needsDefaultToken,
            sessionCount: inspection.sessionCount,
          },
        },
        opts
      );
    }
    req.log.info({ runId, stepId: 'report', summary }, 'OrgReportAgent done');

    return report;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    if (currentStep) {
      const failedStep = currentStep;
      await safeTrack(() =>
        trackStepFailed({
          runId,
          productId: 'lysnrai',
          stepName: failedStep.stepName,
          order: failedStep.order,
          token: opts.token,
          requestId: req.id,
          error: message,
        })
      );
    }
    await safeTrack(() =>
      trackRunFailed({
        runId,
        productId: 'lysnrai',
        name: 'org-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: 'lysnrai.orgs.provision',
  description:
    'A2A pipeline: inspects a LysnrAI organization, checks for missing API tokens and session activity, and returns actionable provisioning recommendations. Flags new orgs needing default token creation, unused tokens, and zero-session orgs needing user outreach. Requires admin role.',
  requiredRole: 'admin',
  inputSchema: z.object({
    orgId: z.string().min(1).describe('Organization ID to provision/inspect'),
  }),
  async execute(args, req) {
    return runOrgProvisioningPipeline(args.orgId, req);
  },
});
