import { ForbiddenException, Injectable, NotFoundException, ServiceUnavailableException } from '@nestjs/common';
import { createHash } from 'crypto';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { LocalStorageAdapter } from './local.adapter';
import { OneDriveStorageAdapter } from './onedrive.adapter';
import { createLogger } from '@core/observability/logging/config/winston.config';
import type { StorageAdapter, UploadResult } from './storage.types';
import type { StorageBinding, StorageBindingScope, StorageBackendKind } from '@prisma/client';

const logger = createLogger('AgentStorageService');

/**
 * 解析默认 storage root：优先 FFAI_STORAGE_LOCAL_ROOT；未配置时按 NODE_ENV 走 fallback。
 * - production / staging：不允许使用 /tmp（重启即丢），强制要求 env 显式声明，否则抛错 fail-fast
 * - dev / test：fallback 到 /tmp/ffai-agent-storage（接受重启丢文件）
 */
function resolveDefaultStorageRoot(): string {
  const explicit = process.env.FFAI_STORAGE_LOCAL_ROOT;
  if (explicit) return explicit;
  const env = process.env.NODE_ENV ?? 'development';
  if (env === 'production' || env === 'staging') {
    throw new Error(
      `FFAI_STORAGE_LOCAL_ROOT must be explicitly set in ${env} environment ` +
        `(non-persistent /tmp default is dev-only)`,
    );
  }
  logger.warn(
    `FFAI_STORAGE_LOCAL_ROOT not set; using non-persistent /tmp/ffai-agent-storage. ` +
      `Files will be lost on restart. Set FFAI_STORAGE_LOCAL_ROOT to a persistent path.`,
  );
  return '/tmp/ffai-agent-storage';
}

@Injectable()
export class StorageService {
  private readonly adapters: Record<StorageBackendKind, StorageAdapter | null>;

  constructor(
    private readonly prisma: PrismaService,
    private readonly local: LocalStorageAdapter,
    private readonly onedrive: OneDriveStorageAdapter,
  ) {
    this.adapters = {
      LOCAL: this.local,
      ONEDRIVE: this.onedrive,
      S3: null,
      GOOGLE_DRIVE: null,
    };
  }

  /**
   * 三层 scope binding 解析：PROJECT > USER > ORGANIZATION。
   * 命中第一个可用 binding 用之；否则创建 LOCAL 默认 binding（per-org 自动 onboarding）。
   */
  async resolveBinding(args: {
    organizationId: string;
    userId: string;
    projectId?: string;
  }): Promise<StorageBinding> {
    const order: Array<{ scope: StorageBindingScope; refId: string | undefined }> = [
      { scope: 'PROJECT', refId: args.projectId },
      { scope: 'USER', refId: args.userId },
      { scope: 'ORGANIZATION', refId: args.organizationId },
    ];
    for (const { scope, refId } of order) {
      if (!refId) continue;
      const bindings = await this.prisma.storageBinding.findMany({
        where: { organizationId: args.organizationId, scope, scopeRefId: refId, enabled: true },
        orderBy: { createdAt: 'desc' },
      });
      for (const b of bindings) {
        const adapter = this.adapters[b.kind];
        if (adapter?.isConfigured(b.config as Record<string, unknown>, b.encryptedSecret)) {
          return b;
        }
      }
    }
    // 兜底：创建/复用 LOCAL ORG default
    const existing = await this.prisma.storageBinding.findFirst({
      where: {
        organizationId: args.organizationId,
        scope: 'ORGANIZATION',
        scopeRefId: args.organizationId,
        kind: 'LOCAL',
        enabled: true,
      },
    });
    if (existing) return existing;
    return this.prisma.storageBinding.create({
      data: {
        organizationId: args.organizationId,
        scope: 'ORGANIZATION',
        scopeRefId: args.organizationId,
        kind: 'LOCAL',
        displayName: '默认本地存储',
        config: { rootPath: `${resolveDefaultStorageRoot()}/${args.organizationId}` } as never,
        createdById: args.userId,
      },
    });
  }

  async upload(args: {
    organizationId: string;
    userId: string;
    projectId?: string;
    path: string;
    content: Buffer;
    mimeType?: string;
  }): Promise<UploadResult> {
    const binding = await this.resolveBinding(args);
    const adapter = this.adapters[binding.kind];
    if (!adapter) throw new ServiceUnavailableException(`No adapter for kind ${binding.kind}`);

    const sha256 = createHash('sha256').update(args.content).digest('hex');
    const adapterResult = await adapter.upload({
      config: binding.config as Record<string, unknown>,
      encryptedSecret: binding.encryptedSecret,
      path: args.path,
      content: args.content,
    });

    const file = await this.prisma.storageFile.create({
      data: {
        bindingId: binding.id,
        organizationId: args.organizationId,
        path: args.path,
        externalId: adapterResult.externalId ?? null,
        sizeBytes: args.content.length,
        mimeType: args.mimeType ?? null,
        sha256,
        encrypted: false,
        uploadedById: args.userId,
      },
    });

    return {
      fileId: file.id,
      externalId: adapterResult.externalId,
      sizeBytes: args.content.length,
      sha256,
    };
  }

  async download(args: { organizationId: string; fileId: string }): Promise<Buffer> {
    const file = await this.prisma.storageFile.findUnique({
      where: { id: args.fileId },
      include: { binding: true },
    });
    if (!file) throw new NotFoundException('File not found');
    if (file.organizationId !== args.organizationId) {
      throw new ForbiddenException('cross-org access denied');
    }
    const adapter = this.adapters[file.binding.kind];
    if (!adapter) throw new ServiceUnavailableException(`No adapter for kind ${file.binding.kind}`);
    return adapter.download({
      config: file.binding.config as Record<string, unknown>,
      encryptedSecret: file.binding.encryptedSecret,
      path: file.path,
      externalId: file.externalId ?? undefined,
    });
  }

  async listFiles(args: { organizationId: string; bindingId?: string }) {
    return this.prisma.storageFile.findMany({
      where: { organizationId: args.organizationId, ...(args.bindingId ? { bindingId: args.bindingId } : {}) },
      orderBy: { createdAt: 'desc' },
      take: 100,
    });
  }
}
