/**
 * GET    /api/schedules/:id     single (decorated)
 * PATCH  /api/schedules/:id     update name / input / schedule fields / enabled
 * DELETE /api/schedules/:id?cancel_inflight=1
 */

import { NextResponse } from 'next/server';
import {
  getSchedule,
  updateSchedule,
  deleteSchedule,
  listInflightForSchedule,
} from '@/lib/schedules/store';
import { decorateSchedule } from '@/lib/schedules/state';
import { listWorkflows, cancelPipeline } from '@/lib/pipeline';
import type { ScheduleKind, ScheduleActionKind, UpdateScheduleInput } from '@/lib/schedules/types';

const VALID_KINDS: ScheduleKind[] = ['period', 'once', 'cron', 'manual'];
const VALID_ACTION_KINDS: ScheduleActionKind[] = ['none', 'chat', 'email', 'telegram'];

export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const s = getSchedule(id);
  if (!s) return NextResponse.json({ error: 'schedule not found' }, { status: 404 });
  return NextResponse.json({ schedule: decorateSchedule(s) });
}

export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const existing = getSchedule(id);
  if (!existing) return NextResponse.json({ error: 'schedule not found' }, { status: 404 });

  let body: any = {};
  try { body = await req.json(); } catch {}

  // body_ref + body_kind are immutable — must build a new schedule
  // to change what the schedule fires. Accept legacy pipeline_name as
  // an alias for the body_ref equality check.
  const incomingBodyRef = body.body_ref ?? body.pipeline_name;
  if (incomingBodyRef && incomingBodyRef !== existing.body_ref) {
    return NextResponse.json({
      error: 'body_ref cannot be changed; create a new schedule instead',
    }, { status: 400 });
  }
  if (body.body_kind && body.body_kind !== existing.body_kind) {
    return NextResponse.json({
      error: 'body_kind cannot be changed; create a new schedule instead',
    }, { status: 400 });
  }

  const patch: UpdateScheduleInput = {};

  if (typeof body.name === 'string') patch.name = body.name.trim();
  if (typeof body.enabled === 'boolean') patch.enabled = body.enabled;

  if (body.input !== undefined) {
    if (!body.input || typeof body.input !== 'object' || Array.isArray(body.input)) {
      return NextResponse.json({ error: 'input must be an object' }, { status: 400 });
    }
    // For pipeline body, validate keys against pipeline.input declarations.
    // Skill / connector_tool bodies have free-form input — no validation here yet.
    if (existing.body_kind === 'pipeline') {
      const workflow = listWorkflows().find((w) => w.name === existing.body_ref);
      if (workflow) {
        const declared = new Set(Object.keys(workflow.input || {}));
        for (const k of Object.keys(body.input)) {
          if (!declared.has(k)) {
            return NextResponse.json({
              error: `input field "${k}" is not declared by pipeline "${existing.body_ref}"`,
            }, { status: 400 });
          }
        }
      }
    }
    patch.input = body.input;
  }

  if (body.skills !== undefined) {
    if (!Array.isArray(body.skills)) {
      return NextResponse.json({ error: 'skills must be an array of skill names' }, { status: 400 });
    }
    patch.skills = body.skills
      .filter((s: unknown) => typeof s === 'string' && s.trim())
      .map((s: string) => s.trim());
  }

  if (body.schedule_kind !== undefined) {
    if (!VALID_KINDS.includes(body.schedule_kind)) {
      return NextResponse.json({ error: 'schedule_kind must be one of: ' + VALID_KINDS.join(', ') }, { status: 400 });
    }
    patch.schedule_kind = body.schedule_kind;
  }
  if (body.schedule_interval_minutes !== undefined && body.schedule_interval_minutes !== null) {
    // null is the client's way of saying "this trigger kind doesn't need
    // an interval" (manual / cron / once), so skip — don't reject it.
    const n = Number(body.schedule_interval_minutes);
    if (!Number.isFinite(n) || n < 1) {
      return NextResponse.json({ error: 'schedule_interval_minutes must be ≥ 1' }, { status: 400 });
    }
    patch.schedule_interval_minutes = Math.max(1, Math.trunc(n));
  }
  if (body.schedule_at !== undefined) patch.schedule_at = body.schedule_at || null;
  if (body.schedule_cron !== undefined) patch.schedule_cron = body.schedule_cron || null;

  // Action — same validation as POST. action_config is replaced wholesale
  // (not merged) when supplied, so the caller must send the full new shape.
  if (body.action_kind !== undefined) {
    if (!VALID_ACTION_KINDS.includes(body.action_kind)) {
      return NextResponse.json({ error: 'action_kind must be one of: ' + VALID_ACTION_KINDS.join(', ') }, { status: 400 });
    }
    patch.action_kind = body.action_kind;
  }
  if (body.action_config !== undefined) {
    if (!body.action_config || typeof body.action_config !== 'object' || Array.isArray(body.action_config)) {
      return NextResponse.json({ error: 'action_config must be an object' }, { status: 400 });
    }
    patch.action_config = body.action_config;
  }
  const effectiveActionKind = patch.action_kind ?? existing.action_kind;
  const effectiveActionConfig = patch.action_config ?? existing.action_config ?? {};
  if (effectiveActionKind === 'chat') {
    const sid = typeof effectiveActionConfig.session_id === 'string' ? effectiveActionConfig.session_id.trim() : '';
    if (!sid) return NextResponse.json({ error: 'action_kind=chat requires action_config.session_id' }, { status: 400 });
  }
  if (effectiveActionKind === 'email') {
    const toRaw = effectiveActionConfig.to;
    const to = Array.isArray(toRaw)
      ? toRaw.filter((x) => typeof x === 'string' && x.trim())
      : (typeof toRaw === 'string' && toRaw.trim() ? [toRaw] : []);
    if (to.length === 0) {
      return NextResponse.json({ error: 'action_kind=email requires action_config.to (recipient address or array)' }, { status: 400 });
    }
  }

  // Cross-field consistency
  const effectiveKind = patch.schedule_kind ?? existing.schedule_kind;
  if (effectiveKind === 'cron' && !(patch.schedule_cron ?? existing.schedule_cron)) {
    return NextResponse.json({ error: 'schedule_cron is required when schedule_kind=cron' }, { status: 400 });
  }
  if (effectiveKind === 'once' && !(patch.schedule_at ?? existing.schedule_at)) {
    return NextResponse.json({ error: 'schedule_at is required when schedule_kind=once' }, { status: 400 });
  }

  updateSchedule(id, patch);
  return NextResponse.json({ schedule: decorateSchedule(getSchedule(id)!) });
}

export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const s = getSchedule(id);
  if (!s) return NextResponse.json({ error: 'schedule not found' }, { status: 404 });

  const url = new URL(req.url);
  const cancelInflight = url.searchParams.get('cancel_inflight') === '1';

  let cancelledPipelines = 0;
  if (cancelInflight) {
    for (const run of listInflightForSchedule(id)) {
      // Phase 1: target_id is always a pipeline_id. When skill /
      // connector_tool body lands, dispatch cancel by body_kind.
      try { if (cancelPipeline(run.target_id)) cancelledPipelines++; }
      catch (e) {
        console.warn(`[schedules/delete] cancelPipeline(${run.target_id}) failed: ${(e as Error).message}`);
      }
    }
  }
  const ok = deleteSchedule(id);
  return NextResponse.json({
    ok,
    cancelled_pipelines: cancelledPipelines,
    cancel_inflight_requested: cancelInflight,
  });
}
