import { Injectable, NotFoundException, ForbiddenException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateBattleMapDto, UpdateBattleMapDto } from './dto';
import * as fs from 'fs';
import * as path from 'path';

@Injectable()
export class BattleMapsService {
  private readonly uploadDir = 'uploads/battle-maps';

  constructor(private prisma: PrismaService) {
    // Ensure upload directory exists
    this.ensureUploadDir();
  }

  private ensureUploadDir() {
    const fullPath = path.resolve(this.uploadDir);
    if (!fs.existsSync(fullPath)) {
      fs.mkdirSync(fullPath, { recursive: true });
    }
  }

  async findAll(campaignId: string, userId: string) {
    await this.verifyAccess(campaignId, userId);

    return this.prisma.battleMap.findMany({
      where: { campaignId },
      orderBy: { createdAt: 'desc' },
    });
  }

  async findOne(campaignId: string, mapId: string, userId: string) {
    await this.verifyAccess(campaignId, userId);

    const map = await this.prisma.battleMap.findFirst({
      where: { id: mapId, campaignId },
    });

    if (!map) {
      throw new NotFoundException('Battle map not found');
    }

    return map;
  }

  async create(
    campaignId: string,
    dto: CreateBattleMapDto,
    imageFile: Express.Multer.File,
    userId: string,
  ) {
    await this.verifyGMAccess(campaignId, userId);

    if (!imageFile) {
      throw new BadRequestException('Map image is required');
    }

    // Generate unique filename
    const ext = path.extname(imageFile.originalname);
    const filename = `${Date.now()}-${Math.random().toString(36).substring(7)}${ext}`;
    const filePath = path.join(this.uploadDir, filename);

    // Save file
    fs.writeFileSync(filePath, imageFile.buffer);

    // Create relative URL for serving
    const imageUrl = `/uploads/battle-maps/${filename}`;

    return this.prisma.battleMap.create({
      data: {
        campaignId,
        name: dto.name,
        imageUrl,
        gridSizeX: dto.gridSizeX ?? 20,
        gridSizeY: dto.gridSizeY ?? 20,
        gridOffsetX: dto.gridOffsetX ?? 0,
        gridOffsetY: dto.gridOffsetY ?? 0,
      },
    });
  }

  async update(campaignId: string, mapId: string, dto: UpdateBattleMapDto, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const map = await this.prisma.battleMap.findFirst({
      where: { id: mapId, campaignId },
    });

    if (!map) {
      throw new NotFoundException('Battle map not found');
    }

    return this.prisma.battleMap.update({
      where: { id: mapId },
      data: {
        name: dto.name,
        gridSizeX: dto.gridSizeX,
        gridSizeY: dto.gridSizeY,
        gridOffsetX: dto.gridOffsetX,
        gridOffsetY: dto.gridOffsetY,
      },
    });
  }

  async delete(campaignId: string, mapId: string, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const map = await this.prisma.battleMap.findFirst({
      where: { id: mapId, campaignId },
    });

    if (!map) {
      throw new NotFoundException('Battle map not found');
    }

    // Delete file from disk
    if (map.imageUrl) {
      const filePath = path.join(process.cwd(), map.imageUrl);
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
    }

    await this.prisma.battleMap.delete({
      where: { id: mapId },
    });

    return { message: 'Battle map deleted' };
  }

  // ==========================================
  // ACCESS HELPERS
  // ==========================================

  private async verifyAccess(campaignId: string, userId: string) {
    const campaign = await this.prisma.campaign.findUnique({
      where: { id: campaignId },
      include: { members: true },
    });

    if (!campaign) {
      throw new NotFoundException('Campaign not found');
    }

    const isGM = campaign.gmId === userId;
    const isMember = campaign.members.some(m => m.userId === userId);

    if (!isGM && !isMember) {
      throw new ForbiddenException('No access to this campaign');
    }

    return { campaign, isGM };
  }

  private async verifyGMAccess(campaignId: string, userId: string) {
    const campaign = await this.prisma.campaign.findUnique({
      where: { id: campaignId },
    });

    if (!campaign) {
      throw new NotFoundException('Campaign not found');
    }

    if (campaign.gmId !== userId) {
      throw new ForbiddenException('Only the GM can perform this action');
    }

    return campaign;
  }
}
