import fs from 'node:fs';
import path from 'node:path';

import type { SiprouterStorage } from './storage.ts';
import type {
  IFaxCompletedEvent,
  IFaxFailedEvent,
  IFaxStartedEvent,
} from './shared/proxy-events.ts';

export interface IFaxJob {
  id: string;
  callId: string;
  number?: string;
  providerId?: string;
  direction: 'outbound' | 'inbound';
  status: 'dialing' | 'started' | 'completed' | 'failed';
  transport?: 'audio' | 't38';
  filePath?: string;
  objectKey?: string;
  codec?: string;
  remoteMedia?: string;
  success?: boolean;
  completionCode?: number | null;
  completionLabel?: string | null;
  error?: string;
  stats?: IFaxCompletedEvent['stats'];
  createdAt: number;
  updatedAt: number;
}

export class FaxJobManager {
  private jobs: IFaxJob[] = [];
  private readonly log: (msg: string) => void;
  private readonly storage: SiprouterStorage;

  constructor(log: (msg: string) => void, storageArg: SiprouterStorage) {
    this.log = log;
    this.storage = storageArg;
  }

  async init(): Promise<void> {
    this.jobs = await this.storage.getFaxJobs();
  }

  async noteDialing(callId: string, number: string, providerId: string): Promise<void> {
    const jobs = this.jobs;
    const now = Date.now();
    const existing = jobs.find((job) => job.callId === callId);
    if (existing) {
      existing.number = number;
      existing.providerId = providerId;
      existing.updatedAt = now;
    } else {
      jobs.unshift({
        id: callId,
        callId,
        number,
        providerId,
        direction: 'outbound',
        status: 'dialing',
        createdAt: now,
        updatedAt: now,
      });
    }
    await this.writeJobs();
  }

  async noteStarted(event: IFaxStartedEvent): Promise<void> {
    const now = Date.now();
    const job = this.getOrCreateJob(event.call_id, event.direction, now);
    job.status = 'started';
    job.transport = event.transport;
    job.filePath = event.file_path;
    await this.ensureOutboundFileObject(job, event.file_path);
    job.codec = event.codec;
    job.remoteMedia = event.remote_media;
    job.updatedAt = now;
    await this.writeJobs();
  }

  async noteCompleted(event: IFaxCompletedEvent): Promise<void> {
    const now = Date.now();
    const job = this.getOrCreateJob(event.call_id, event.direction, now);
    job.status = 'completed';
    job.transport = event.transport;
    job.filePath = event.file_path;
    await this.ensureOutboundFileObject(job, event.file_path);
    job.codec = event.codec;
    job.success = event.success;
    job.completionCode = event.completion_code ?? null;
    job.completionLabel = event.completion_label ?? null;
    job.stats = event.stats;
    job.updatedAt = now;
    await this.writeJobs();
  }

  async noteFailed(event: IFaxFailedEvent): Promise<void> {
    const now = Date.now();
    const job = this.getOrCreateJob(event.call_id, event.direction, now);
    job.status = 'failed';
    job.transport = event.transport;
    job.filePath = event.file_path;
    await this.ensureOutboundFileObject(job, event.file_path);
    job.error = event.error;
    job.success = false;
    job.updatedAt = now;
    await this.writeJobs();
  }

  getJobs(): IFaxJob[] {
    return [...this.jobs];
  }

  private getOrCreateJob(
    callId: string,
    direction: 'outbound' | 'inbound',
    now: number,
  ): IFaxJob {
    let job = this.jobs.find((entry) => entry.callId === callId);
    if (!job) {
      job = {
        id: callId,
        callId,
        direction,
        status: 'dialing',
        createdAt: now,
        updatedAt: now,
      };
      this.jobs.unshift(job);
    }
    return job;
  }

  private async ensureOutboundFileObject(jobArg: IFaxJob, filePathArg: string | undefined): Promise<void> {
    if (jobArg.direction !== 'outbound' || jobArg.objectKey || !filePathArg) return;

    const localPath = path.isAbsolute(filePathArg) ? filePathArg : path.join(process.cwd(), filePathArg);
    if (!fs.existsSync(localPath)) return;

    const extension = path.extname(localPath) || '.tif';
    jobArg.objectKey = await this.storage.putFileObject(`fax/outbound/${jobArg.callId}${extension}`, localPath);
  }

  private async writeJobs(): Promise<void> {
    await this.storage.writeFaxJobs(this.jobs);
    this.log(`[fax] persisted ${this.jobs.length} job(s)`);
  }
}
