import * as plugins from '../../plugins.js';
import { SimpleLocalClient, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import { LocalFileMapper } from './local_file.mapper.js';
import type { ILocalFileConfig, ILocalFileSnapshot } from './local_file.types.js';
import { localFileProfile } from './local_file.types.js';

export class LocalFileClient extends SimpleLocalClient<ILocalFileConfig> {
  private currentFilePath?: string;

  constructor(private readonly configArg: ILocalFileConfig) {
    super(localFileProfile, configArg);
    this.currentFilePath = this.filePath();
  }

  public async getSnapshot(forceRefreshArg = false): Promise<ILocalFileSnapshot> {
    if (this.shouldReadLocalFile()) {
      try {
        return LocalFileMapper.toSnapshot({ config: this.configWithCurrentPath(), rawData: await this.readLocalImage(), online: true, source: 'manual' });
      } catch (errorArg) {
        return LocalFileMapper.toSnapshot({ config: this.configWithCurrentPath(), online: false, source: 'runtime', error: errorMessage(errorArg) });
      }
    }

    const snapshot = await super.getSnapshot(forceRefreshArg);
    if (snapshot.rawData === undefined && snapshot.entities.length) {
      return LocalFileMapper.toSnapshot({ config: { ...this.configWithCurrentPath(), snapshot }, online: snapshot.online, source: snapshot.source, error: snapshot.error });
    }

    return LocalFileMapper.toSnapshot({ config: this.configWithCurrentPath(), rawData: snapshot.rawData, online: snapshot.online, source: snapshot.source, error: snapshot.error });
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service !== 'update_file_path') {
      return super.execute(requestArg);
    }

    const nextPath = stringValue(requestArg.data?.file_path) || stringValue(requestArg.data?.filePath) || stringValue(requestArg.data?.path);
    if (!nextPath) {
      return { success: false, error: 'Local File update_file_path requires data.file_path, data.filePath, or data.path.' };
    }

    try {
      await plugins.fs.access(nextPath);
      this.currentFilePath = nextPath;
      this.configArg.filePath = nextPath;
      this.configArg.file_path = nextPath;
      return { success: true, data: await this.getSnapshot(true) };
    } catch (errorArg) {
      return { success: false, error: `Path ${nextPath} is not accessible: ${errorMessage(errorArg)}` };
    }
  }

  private shouldReadLocalFile(): boolean {
    return Boolean(this.currentFilePath && !this.configArg.snapshot && this.configArg.rawData === undefined && this.configArg.rawText === undefined && !this.configArg.entities?.length && !this.configArg.client && !this.configArg.snapshotProvider && !this.configArg.host);
  }

  private async readLocalImage(): Promise<Record<string, unknown>> {
    const filePath = this.currentFilePath;
    if (!filePath) {
      throw new Error('Local File snapshots require config.filePath or config.file_path.');
    }

    const [content, stat] = await Promise.all([
      plugins.fs.readFile(filePath),
      plugins.fs.stat(filePath),
    ]);
    if (!stat.isFile()) {
      throw new Error(`Local File snapshots require a file path, not a directory: ${filePath}`);
    }

    return {
      filePath,
      file_path: filePath,
      contentType: contentTypeForPath(filePath),
      imageBytes: content.length,
      imageBase64: content.toString('base64'),
      updatedAt: stat.mtime.toISOString(),
    };
  }

  private filePath(): string | undefined {
    const metadata = this.configArg.metadata;
    return stringValue(this.configArg.filePath) || stringValue(this.configArg.file_path) || stringValue(metadata?.filePath) || stringValue(metadata?.file_path);
  }

  private configWithCurrentPath(): ILocalFileConfig {
    return { ...this.configArg, filePath: this.currentFilePath, file_path: this.currentFilePath };
  }
}

const contentTypesByExtension: Record<string, string> = {
  '.bmp': 'image/bmp',
  '.gif': 'image/gif',
  '.jpeg': 'image/jpeg',
  '.jpg': 'image/jpeg',
  '.png': 'image/png',
  '.svg': 'image/svg+xml',
  '.webp': 'image/webp',
};

const contentTypeForPath = (pathArg: string): string => contentTypesByExtension[plugins.path.extname(pathArg).toLowerCase()] || 'application/octet-stream';

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
