import {
  Injectable,
  Logger,
  OnModuleDestroy,
  OnModuleInit,
} from '@nestjs/common';
import { Client, Connection, ScheduleNotFoundError } from '@temporalio/client';

const TEMPORAL_ADDRESS = process.env.TEMPORAL_ADDRESS || 'localhost:7233';
const TEMPORAL_NAMESPACE = process.env.TEMPORAL_NAMESPACE || 'default';
const TASK_QUEUE = process.env.TEMPORAL_TASK_QUEUE || 'workflow-execution';

const cronScheduleId = (workflowId: number) => `wf-${workflowId}-cron`;

@Injectable()
export class TemporalService implements OnModuleInit, OnModuleDestroy {
  private connection: Connection;
  private client: Client;
  private readonly logger = new Logger(TemporalService.name);

  async onModuleInit() {
    try {
      this.connection = await Connection.connect({ address: TEMPORAL_ADDRESS });
      this.client = new Client({
        connection: this.connection,
        namespace: TEMPORAL_NAMESPACE,
      });
      this.logger.log(`Connected to Temporal at ${TEMPORAL_ADDRESS}`);
    } catch (err) {
      this.logger.warn(
        `Failed to connect to Temporal at ${TEMPORAL_ADDRESS}: ${err.message}. Workflow execution will be unavailable.`,
      );
    }
  }

  async onModuleDestroy() {
    await this.connection?.close();
  }

  /**
   * Start a workflow execution via Temporal.
   */
  async startWorkflow(params: {
    instanceId: number;
    workflowId: number;
    versionId: number;
    dsl: any;
    context: Record<string, any>;
  }): Promise<{ temporalRunId: string }> {
    if (!this.client) {
      throw new Error('Temporal client not connected');
    }

    const workflowId = `wf-${params.workflowId}-${params.instanceId}`;

    const handle = await this.client.workflow.start('dslWorkflow', {
      taskQueue: TASK_QUEUE,
      workflowId,
      args: [params],
    });

    return { temporalRunId: handle.workflowId };
  }

  /**
   * Cancel a running workflow.
   */
  async cancelWorkflow(temporalRunId: string): Promise<void> {
    if (!this.client) throw new Error('Temporal client not connected');
    const handle = this.client.workflow.getHandle(temporalRunId);
    await handle.cancel();
  }

  /**
   * Send a signal to a running workflow (e.g., approval).
   */
  async signalWorkflow(
    temporalRunId: string,
    signalName: string,
    payload: any,
  ): Promise<void> {
    if (!this.client) throw new Error('Temporal client not connected');
    const handle = this.client.workflow.getHandle(temporalRunId);
    await handle.signal(signalName, payload);
  }

  /**
   * Query a workflow for its current progress.
   */
  async queryWorkflow(temporalRunId: string, queryName: string): Promise<any> {
    if (!this.client) throw new Error('Temporal client not connected');
    const handle = this.client.workflow.getHandle(temporalRunId);
    return await handle.query(queryName);
  }

  /**
   * Check if Temporal client is available.
   */
  isAvailable(): boolean {
    return !!this.client;
  }

  // ─── Cron Schedules ────────────────────────────────────────────────────────

  /**
   * Create or update a Temporal Schedule that periodically starts cronTriggerWorkflow.
   * Idempotent — safe to call on every publish.
   */
  async upsertCronSchedule(input: {
    workflowId: number;
    versionId: number;
    dsl: any;
    cron: string;
    timezone?: string;
  }): Promise<void> {
    if (!this.client) throw new Error('Temporal client not connected');

    const scheduleId = cronScheduleId(input.workflowId);
    const action = {
      type: 'startWorkflow' as const,
      workflowType: 'cronTriggerWorkflow',
      taskQueue: TASK_QUEUE,
      args: [
        {
          workflowId: input.workflowId,
          versionId: input.versionId,
          dsl: input.dsl,
          scheduleId,
        },
      ],
    };
    const spec = {
      cronExpressions: [input.cron],
      ...(input.timezone ? { timezoneName: input.timezone } : {}),
    };

    const handle = this.client.schedule.getHandle(scheduleId);
    try {
      await handle.describe();
      // Exists — update spec + action so DSL/version changes propagate
      await handle.update((prev) => ({
        ...prev,
        spec,
        action,
      }));
      this.logger.log(
        `Updated cron schedule ${scheduleId} (cron="${input.cron}")`,
      );
    } catch (err: any) {
      if (err instanceof ScheduleNotFoundError) {
        await this.client.schedule.create({
          scheduleId,
          spec,
          action,
        });
        this.logger.log(
          `Created cron schedule ${scheduleId} (cron="${input.cron}")`,
        );
      } else {
        throw err;
      }
    }
  }

  /**
   * Pause a cron schedule. No-op if it doesn't exist.
   */
  async pauseCronSchedule(workflowId: number): Promise<void> {
    if (!this.client) return;
    const scheduleId = cronScheduleId(workflowId);
    try {
      await this.client.schedule.getHandle(scheduleId).pause();
      this.logger.log(`Paused cron schedule ${scheduleId}`);
    } catch (err: any) {
      if (err instanceof ScheduleNotFoundError) return;
      throw err;
    }
  }

  /**
   * Resume a paused cron schedule. No-op if it doesn't exist.
   */
  async unpauseCronSchedule(workflowId: number): Promise<void> {
    if (!this.client) return;
    const scheduleId = cronScheduleId(workflowId);
    try {
      await this.client.schedule.getHandle(scheduleId).unpause();
      this.logger.log(`Unpaused cron schedule ${scheduleId}`);
    } catch (err: any) {
      if (err instanceof ScheduleNotFoundError) return;
      throw err;
    }
  }

  /**
   * Delete a cron schedule. No-op if it doesn't exist.
   */
  async deleteCronSchedule(workflowId: number): Promise<void> {
    if (!this.client) return;
    const scheduleId = cronScheduleId(workflowId);
    try {
      await this.client.schedule.getHandle(scheduleId).delete();
      this.logger.log(`Deleted cron schedule ${scheduleId}`);
    } catch (err: any) {
      if (err instanceof ScheduleNotFoundError) return;
      throw err;
    }
  }
}
