import { Repository, FindOptionsWhere, In, IsNull } from 'typeorm';
import { StorageBucket, StorageFolder, StorageFile } from '../entities';

export interface FileSearchFilters {
  category?: string;
  mimeType?: string;
  folderId?: string;
  isPublic?: boolean;
  uploadedBy?: string;
}

export class StorageService {
  constructor(
    private readonly bucketRepository: Repository<StorageBucket>,
    private readonly folderRepository: Repository<StorageFolder>,
    private readonly fileRepository: Repository<StorageFile>
  ) {}

  // ============================================
  // BUCKETS
  // ============================================

  async findAllBuckets(_tenantId?: string): Promise<StorageBucket[]> {
    // Note: Buckets are system-level resources, not tenant-specific
    return this.bucketRepository.find({
      where: { isActive: true },
      order: { name: 'ASC' },
    });
  }

  async findBucket(id: string): Promise<StorageBucket | null> {
    return this.bucketRepository.findOne({ where: { id } });
  }

  async findBucketByName(_tenantId: string, name: string): Promise<StorageBucket | null> {
    // Note: Buckets are system-level resources, not tenant-specific
    return this.bucketRepository.findOne({ where: { name } });
  }

  async createBucket(
    _tenantId: string,
    data: Partial<StorageBucket>,
    _createdBy?: string
  ): Promise<StorageBucket> {
    // Note: Buckets are system-level resources, not tenant-specific
    const bucket = this.bucketRepository.create(data);
    return this.bucketRepository.save(bucket);
  }

  async updateBucket(id: string, data: Partial<StorageBucket>): Promise<StorageBucket | null> {
    const bucket = await this.findBucket(id);
    if (!bucket) return null;

    Object.assign(bucket, data);
    return this.bucketRepository.save(bucket);
  }

  async deleteBucket(id: string): Promise<boolean> {
    // Check if bucket has files
    const fileCount = await this.fileRepository.count({ where: { bucketId: id } });
    if (fileCount > 0) {
      throw new Error('Cannot delete bucket with files. Delete files first.');
    }

    const result = await this.bucketRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  // Note: StorageBucket entity doesn't track usage stats
  // Usage should be computed from files on-demand
  async updateBucketUsage(_id: string, _sizeChange: number, _fileCountChange: number): Promise<void> {
    // No-op: Bucket usage stats computed on-demand from files
  }

  // ============================================
  // FOLDERS
  // ============================================

  async findFoldersInBucket(bucketId: string, parentId?: string): Promise<StorageFolder[]> {
    const where: FindOptionsWhere<StorageFolder> = { bucketId };
    if (parentId) {
      where.parentId = parentId;
    } else {
      where.parentId = IsNull();
    }

    return this.folderRepository.find({
      where,
      order: { name: 'ASC' },
    });
  }

  async findFolder(id: string): Promise<StorageFolder | null> {
    return this.folderRepository.findOne({ where: { id } });
  }

  async findFolderByPath(bucketId: string, path: string): Promise<StorageFolder | null> {
    return this.folderRepository.findOne({ where: { bucketId, path } });
  }

  async createFolder(
    tenantId: string,
    bucketId: string,
    data: Partial<StorageFolder>,
    createdBy?: string
  ): Promise<StorageFolder> {
    const folder = this.folderRepository.create({
      ...data,
      tenantId,
      bucketId,
      createdBy,
    });
    return this.folderRepository.save(folder);
  }

  async updateFolder(id: string, data: Partial<StorageFolder>): Promise<StorageFolder | null> {
    const folder = await this.findFolder(id);
    if (!folder) return null;

    Object.assign(folder, data);
    return this.folderRepository.save(folder);
  }

  async deleteFolder(id: string, recursive: boolean = false): Promise<boolean> {
    const folder = await this.findFolder(id);
    if (!folder) return false;

    // Check for children
    const childFolders = await this.folderRepository.count({ where: { parentId: id } });
    const childFiles = await this.fileRepository.count({ where: { folderId: id } });

    if ((childFolders > 0 || childFiles > 0) && !recursive) {
      throw new Error('Folder is not empty. Use recursive delete or remove contents first.');
    }

    if (recursive) {
      // Delete all files in folder
      await this.fileRepository.softDelete({ folderId: id });
      // Delete all subfolders (recursive deletion would need proper implementation)
      await this.folderRepository.softDelete({ parentId: id });
    }

    const result = await this.folderRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  // ============================================
  // FILES
  // ============================================

  async findFilesInFolder(
    bucketId: string,
    folderId?: string,
    filters: FileSearchFilters = {}
  ): Promise<StorageFile[]> {
    const where: FindOptionsWhere<StorageFile> = { bucketId };

    if (folderId) {
      where.folderId = folderId;
    } else {
      where.folderId = IsNull();
    }

    if (filters.category) where.category = filters.category as any;
    if (filters.mimeType) where.mimeType = filters.mimeType;
    if (filters.isPublic !== undefined) where.isPublic = filters.isPublic;
    if (filters.uploadedBy) where.uploadedBy = filters.uploadedBy;

    return this.fileRepository.find({
      where,
      order: { originalName: 'ASC' },
    });
  }

  async findFile(id: string): Promise<StorageFile | null> {
    return this.fileRepository.findOne({
      where: { id },
      relations: ['bucket', 'folder'],
    });
  }

  async findFileByStorageKey(storageKey: string): Promise<StorageFile | null> {
    return this.fileRepository.findOne({ where: { storageKey } });
  }

  async createFile(
    tenantId: string,
    bucketId: string,
    data: Partial<StorageFile>,
    uploadedBy?: string
  ): Promise<StorageFile> {
    const file = this.fileRepository.create({
      ...data,
      tenantId,
      bucketId,
      uploadedBy,
      status: 'active',
    });

    const savedFile = await this.fileRepository.save(file);

    // Update bucket usage
    await this.updateBucketUsage(bucketId, data.sizeBytes || 0, 1);

    return savedFile;
  }

  async updateFile(id: string, data: Partial<StorageFile>): Promise<StorageFile | null> {
    const file = await this.findFile(id);
    if (!file) return null;

    Object.assign(file, data);
    return this.fileRepository.save(file);
  }

  async deleteFile(id: string): Promise<boolean> {
    const file = await this.findFile(id);
    if (!file) return false;

    // Update bucket usage
    await this.updateBucketUsage(file.bucketId, -(file.sizeBytes || 0), -1);

    const result = await this.fileRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async incrementDownloadCount(id: string): Promise<void> {
    await this.fileRepository
      .createQueryBuilder()
      .update()
      .set({
        accessCount: () => 'access_count + 1',
        lastAccessedAt: new Date(),
      })
      .where('id = :id', { id })
      .execute();
  }

  async updateProcessingStatus(
    id: string,
    status: string,
    metadata?: Record<string, any>
  ): Promise<void> {
    const updates: Partial<StorageFile> = { processingStatus: status as any };
    if (metadata) {
      updates.metadata = metadata;
    }
    // Note: processingStatus completion is tracked via updatedAt
    await this.fileRepository.update(id, updates);
  }

  // ============================================
  // SEARCH & QUERIES
  // ============================================

  async searchFiles(
    tenantId: string,
    query: string,
    filters: FileSearchFilters = {}
  ): Promise<StorageFile[]> {
    const qb = this.fileRepository
      .createQueryBuilder('file')
      .where('file.tenant_id = :tenantId', { tenantId })
      .andWhere(
        '(file.original_name ILIKE :query OR file.description ILIKE :query)',
        { query: `%${query}%` }
      );

    if (filters.category) {
      qb.andWhere('file.category = :category', { category: filters.category });
    }
    if (filters.mimeType) {
      qb.andWhere('file.mime_type = :mimeType', { mimeType: filters.mimeType });
    }
    if (filters.isPublic !== undefined) {
      qb.andWhere('file.is_public = :isPublic', { isPublic: filters.isPublic });
    }

    return qb.orderBy('file.created_at', 'DESC').take(100).getMany();
  }

  async getStorageUsage(tenantId: string): Promise<{
    totalBytes: number;
    fileCount: number;
    byCategory: Record<string, { bytes: number; count: number }>;
  }> {
    // Compute storage usage from files (buckets don't track usage stats)
    const usageResult = await this.fileRepository
      .createQueryBuilder('file')
      .select('SUM(file.size_bytes)', 'totalBytes')
      .addSelect('COUNT(*)', 'fileCount')
      .where('file.tenant_id = :tenantId', { tenantId })
      .andWhere('file.status != :status', { status: 'deleted' })
      .getRawOne();

    const totalBytes = Number(usageResult?.totalBytes || 0);
    const fileCount = Number(usageResult?.fileCount || 0);

    const categoryStats = await this.fileRepository
      .createQueryBuilder('file')
      .select('file.category', 'category')
      .addSelect('SUM(file.size_bytes)', 'bytes')
      .addSelect('COUNT(*)', 'count')
      .where('file.tenant_id = :tenantId', { tenantId })
      .groupBy('file.category')
      .getRawMany();

    const byCategory: Record<string, { bytes: number; count: number }> = {};
    for (const stat of categoryStats) {
      byCategory[stat.category || 'uncategorized'] = {
        bytes: parseInt(stat.bytes) || 0,
        count: parseInt(stat.count) || 0,
      };
    }

    return { totalBytes, fileCount, byCategory };
  }

  async findRecentFiles(tenantId: string, limit: number = 20): Promise<StorageFile[]> {
    return this.fileRepository.find({
      where: { tenantId },
      order: { createdAt: 'DESC' },
      take: limit,
    });
  }
}
