import { Injectable, NotFoundException, InternalServerErrorException, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { createHash } from 'crypto';
import { mkdirSync, writeFileSync } from 'fs';
import { join } from 'path';
import { MapLibrary } from '../../entities/map-library.entity';
import { MapRotation } from '../../entities/map-rotation.entity';
import { UpdateRotationDto } from './dto/update-rotation.dto';
import { UploadMapDto } from './dto/upload-map.dto';

// Docker volume mount point for map storage. Tenant-scoped subdirectory enforces isolation.
const MAP_DATA_ROOT = process.env.MAP_DATA_PATH || '/app/map_data';

@Injectable()
export class MapsService {
  private readonly logger = new Logger(MapsService.name);

  constructor(
    @InjectRepository(MapLibrary)
    private readonly mapLibraryRepo: Repository<MapLibrary>,
    @InjectRepository(MapRotation)
    private readonly mapRotationRepo: Repository<MapRotation>,
  ) {}

  async uploadMap(
    licenseId: string,
    dto: UploadMapDto,
    file: Express.Multer.File,
  ): Promise<MapLibrary> {
    const checksum = createHash('sha256').update(file.buffer).digest('hex');

    // Build tenant-scoped storage path: /app/map_data/{licenseId}/{timestamp}_{filename}
    const filename = `${Date.now()}_${file.originalname}`;
    const tenantDir = join(MAP_DATA_ROOT, licenseId);
    const absolutePath = join(tenantDir, filename);

    // Relative storage path stored in DB — avoids coupling to the absolute mount point
    const storagePath = `/map_data/${licenseId}/${filename}`;

    try {
      mkdirSync(tenantDir, { recursive: true });
      writeFileSync(absolutePath, file.buffer);
      this.logger.log(`Map uploaded: ${absolutePath} (${file.size} bytes)`);
    } catch (err) {
      this.logger.error(`Failed to write map file to disk: ${absolutePath}`, err);
      throw new InternalServerErrorException('Failed to save map file to storage');
    }

    const map = this.mapLibraryRepo.create({
      license_id: licenseId,
      filename: file.originalname,
      display_name: dto.display_name,
      storage_path: storagePath,
      file_size_bytes: file.size,
      map_type: dto.map_type,
      seed: dto.seed ?? null,
      world_size: dto.world_size ?? null,
      thumbnail_path: null,
      checksum,
    });

    return this.mapLibraryRepo.save(map);
  }

  async getMaps(licenseId: string): Promise<{ maps: MapLibrary[] }> {
    const maps = await this.mapLibraryRepo.find({
      where: { license_id: licenseId },
      order: { uploaded_at: 'DESC' },
    });
    return { maps };
  }

  async deleteMap(licenseId: string, mapId: string): Promise<void> {
    const result = await this.mapLibraryRepo.delete({
      id: mapId,
      license_id: licenseId,
    });

    if (result.affected === 0) {
      throw new NotFoundException(`Map ${mapId} not found`);
    }
  }

  async getRotation(licenseId: string): Promise<MapRotation[]> {
    return this.mapRotationRepo.find({
      where: { license_id: licenseId },
      relations: ['map'],
      order: { rotation_order: 'ASC' },
    });
  }

  async updateRotation(licenseId: string, dto: UpdateRotationDto): Promise<MapRotation[]> {
    // Delete existing rotation entries for this license
    await this.mapRotationRepo.delete({ license_id: licenseId });

    // Create new rotation entries
    const rotationEntries = dto.maps.map((entry) =>
      this.mapRotationRepo.create({
        license_id: licenseId,
        map_id: entry.map_id,
        rotation_order: entry.rotation_order,
        is_active: true,
      }),
    );

    await this.mapRotationRepo.save(rotationEntries);

    // Return updated rotation with map data
    return this.getRotation(licenseId);
  }
}
