import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WipeProfile } from '../../entities/wipe-profile.entity';
import { WipeSchedule } from '../../entities/wipe-schedule.entity';
import { WipeHistory } from '../../entities/wipe-history.entity';
import { CreateProfileDto } from './dto/create-profile.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { CreateScheduleDto } from './dto/create-schedule.dto';
import { TriggerWipeDto } from './dto/trigger-wipe.dto';
import { NatsService } from '../../services/nats.service';

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

  constructor(
    @InjectRepository(WipeProfile)
    private readonly wipeProfileRepo: Repository<WipeProfile>,
    @InjectRepository(WipeSchedule)
    private readonly wipeScheduleRepo: Repository<WipeSchedule>,
    @InjectRepository(WipeHistory)
    private readonly wipeHistoryRepo: Repository<WipeHistory>,
    private readonly natsService: NatsService,
  ) {}

  async getProfiles(licenseId: string): Promise<WipeProfile[]> {
    return this.wipeProfileRepo.find({
      where: { license_id: licenseId },
      order: { created_at: 'DESC' },
    });
  }

  async createProfile(licenseId: string, dto: CreateProfileDto): Promise<WipeProfile> {
    const profile = this.wipeProfileRepo.create({
      license_id: licenseId,
      ...dto,
    });
    return this.wipeProfileRepo.save(profile);
  }

  async updateProfile(
    licenseId: string,
    profileId: string,
    dto: UpdateProfileDto,
  ): Promise<WipeProfile> {
    const profile = await this.wipeProfileRepo.findOne({
      where: { id: profileId, license_id: licenseId },
    });

    if (!profile) {
      throw new NotFoundException(`Wipe profile ${profileId} not found`);
    }

    Object.assign(profile, dto);
    profile.updated_at = new Date();
    return this.wipeProfileRepo.save(profile);
  }

  async deleteProfile(licenseId: string, profileId: string): Promise<void> {
    const result = await this.wipeProfileRepo.delete({
      id: profileId,
      license_id: licenseId,
    });

    if (result.affected === 0) {
      throw new NotFoundException(`Wipe profile ${profileId} not found`);
    }
  }

  async getSchedules(licenseId: string): Promise<WipeSchedule[]> {
    return this.wipeScheduleRepo.find({
      where: { license_id: licenseId },
      relations: ['wipe_profile'],
      order: { created_at: 'DESC' },
    });
  }

  async createSchedule(licenseId: string, dto: CreateScheduleDto): Promise<WipeSchedule> {
    const schedule = this.wipeScheduleRepo.create({
      license_id: licenseId,
      ...dto,
    });
    return this.wipeScheduleRepo.save(schedule);
  }

  async getHistory(licenseId: string, limit: number = 50): Promise<WipeHistory[]> {
    return this.wipeHistoryRepo.find({
      where: { license_id: licenseId },
      relations: ['wipe_profile', 'wipe_schedule', 'map'],
      order: { created_at: 'DESC' },
      take: limit,
    });
  }

  async triggerWipe(
    licenseId: string,
    dto: TriggerWipeDto,
  ): Promise<{ wipe_history_id: string }> {
    const history = this.wipeHistoryRepo.create({
      license_id: licenseId,
      wipe_type: dto.wipe_type,
      wipe_profile_id: dto.wipe_profile_id,
      trigger_type: 'manual',
      status: 'pending',
    });

    const saved = await this.wipeHistoryRepo.save(history);

    await this.natsService.publish(`corrosion.${licenseId}.cmd.wipe`, {
      wipe_history_id: saved.id,
      wipe_type: dto.wipe_type,
      wipe_profile_id: dto.wipe_profile_id ?? null,
      trigger_type: 'manual',
      timestamp: new Date().toISOString(),
    });
    this.logger.log(`Wipe triggered for license ${licenseId} — history id ${saved.id}`);

    return { wipe_history_id: saved.id };
  }

  async triggerDryRun(
    licenseId: string,
    dto: TriggerWipeDto,
  ): Promise<{
    would_delete: string[];
    would_preserve: string[];
    estimated_duration_seconds: number;
    profile_name: string | null;
    notes: string[];
  }> {
    // Resolve profile config if a profile ID was supplied.
    let profile: WipeProfile | null = null;
    if (dto.wipe_profile_id) {
      profile = await this.wipeProfileRepo.findOne({
        where: { id: dto.wipe_profile_id, license_id: licenseId },
      });
    }

    if (!profile && dto.wipe_profile_id) {
      throw new NotFoundException(`Wipe profile ${dto.wipe_profile_id} not found`);
    }

    const notes: string[] = [];

    // Base files affected by all wipe types.
    const would_delete: string[] = ['*.map', '*.sav'];
    const would_preserve: string[] = [
      'oxide/',
      'oxide/plugins/',
      'cfg/',
      'server.cfg',
    ];

    // Blueprint wipe additions.
    if (dto.wipe_type === 'blueprint' || dto.wipe_type === 'full') {
      would_delete.push('player.blueprints.db', 'player.tech.db');
    }

    // Full wipe: also clear player data and oxide data.
    if (dto.wipe_type === 'full') {
      would_delete.push(
        'player.deaths.db',
        'player.identities.db',
        'player.states.db',
        'player.tokens.db',
        'oxide/data/*',
      );
      would_preserve.splice(would_preserve.indexOf('oxide/'), 1);
    }

    // Factor in pre_wipe_config from the profile (if set).
    let estimatedSeconds = 45;

    if (profile) {
      const pre = profile.pre_wipe_config as Record<string, any>;
      const post = profile.post_wipe_config as Record<string, any>;

      if (pre?.backup_before_wipe) {
        estimatedSeconds += 60;
        notes.push('Pre-wipe backup will run before deletion (+60s)');
        would_preserve.push('backups/');
      }

      if (pre?.kick_players_before_wipe) {
        const countdownWarnings: number[] = (pre.countdown_warnings as number[]) ?? [];
        const maxWarning = countdownWarnings.length > 0 ? Math.max(...countdownWarnings) : 0;
        if (maxWarning > 0) {
          estimatedSeconds += maxWarning * 60;
          notes.push(`Players will be warned ${countdownWarnings.join(', ')} minutes before kick (+${maxWarning * 60}s)`);
        }
      }

      if (post?.verify_server_started) {
        estimatedSeconds += 30;
        notes.push('Post-wipe: server health check will run (+30s)');
      }

      if (post?.rollback_on_failure) {
        notes.push('Rollback on failure is enabled — backup will be preserved if wipe fails');
      }

      if (post?.max_restart_attempts) {
        const attempts = post.max_restart_attempts as number;
        if (attempts > 1) {
          estimatedSeconds += (attempts - 1) * 15;
          notes.push(`Up to ${attempts} restart attempts (+${(attempts - 1) * 15}s max)`);
        }
      }
    } else {
      notes.push('No profile selected — using default wipe behavior');
    }

    // Account for world size in time estimate.
    // Larger worlds take longer to clear from disk (rough heuristic).
    // We don't have world_size here without querying server_config,
    // so apply a static estimate per wipe type.
    if (dto.wipe_type === 'full') {
      estimatedSeconds += 75;
    } else if (dto.wipe_type === 'blueprint') {
      estimatedSeconds += 10;
    }

    this.logger.log(
      `Dry-run for license ${licenseId}: type=${dto.wipe_type}, ` +
      `profile=${profile?.profile_name ?? 'none'}, estimated=${estimatedSeconds}s`,
    );

    return {
      would_delete,
      would_preserve,
      estimated_duration_seconds: estimatedSeconds,
      profile_name: profile?.profile_name ?? null,
      notes,
    };
  }
}
