import {
  Injectable,
  NotFoundException,
  ConflictException,
  Logger,
  BadRequestException,
  Inject,
} from '@nestjs/common';
import * as crypto from 'crypto';

import { Department } from '../../database/models/department.model';
import { CreateDepartmentDto } from './dto/create-department.dto';
import { UpdateDepartmentDto } from './dto/update-department.dto';
import { PaginationDto } from '../../common/dto/pagination.dto';
import { DepartmentStatsDto } from './dto/department-stats.dto';
import { generateApiKey } from '../../common/utils/crypto.util';
import { paginate, PaginatedResult } from '../../common/utils/pagination.util';
import { WalletService } from '../blockchain/wallet.service';

export interface ApiKeyPair {
  apiKey: string;
  apiSecret: string;
}

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

  constructor(
    @Inject(Department)
    private readonly departmentRepository: typeof Department,
    private readonly walletService: WalletService,
  ) {}

  /**
   * Create a new department with API key pair
   * @param dto - Create department data
   * @returns Promise containing department and API key pair
   */
  async create(
    dto: CreateDepartmentDto,
  ): Promise<{ department: Department; apiKey: string; apiSecret: string }> {
    this.logger.debug(`Creating department with code: ${dto.code}`);

    // Validate code format (uppercase, numbers, and underscores only)
    if (!/^[A-Z0-9_]+$/.test(dto.code)) {
      throw new BadRequestException(
        'Department code must contain only uppercase letters, numbers, and underscores',
      );
    }

    // Check if department already exists
    const existingDepartment = await this.departmentRepository.query().findOne({
      code: dto.code,
    });

    if (existingDepartment) {
      throw new BadRequestException(`Department with code ${dto.code} already exists`);
    }

    // Generate API key pair
    const { apiKey, apiSecret, apiKeyHash, apiSecretHash } = await generateApiKey();

    // Create department first
    const savedDepartment = await this.departmentRepository.query().insertAndFetch({
      code: dto.code,
      name: dto.name,
      apiKeyHash,
      apiSecretHash,
      webhookSecretHash: crypto.randomBytes(32).toString('hex'),
      description: dto.description,
      contactEmail: dto.contactEmail,
      contactPhone: dto.contactPhone,
      webhookUrl: dto.webhookUrl,
    });

    // Create wallet for the department
    const wallet = await this.walletService.createWallet('DEPARTMENT', savedDepartment.id);

    // Update department with wallet address and refetch
    const updatedDepartment = await savedDepartment.$query().patchAndFetch({
      walletAddress: wallet.address,
    });

    this.logger.log(
      `Department created successfully: ${updatedDepartment.id} with wallet: ${wallet.address}`,
    );

    return {
      department: updatedDepartment,
      apiKey,
      apiSecret,
    };
  }

  /**
   * Find all departments with pagination
   * @param query - Pagination query
   * @returns Promise<PaginatedResult<Department>>
   */
  async findAll(query: PaginationDto): Promise<PaginatedResult<Department>> {
    this.logger.debug(`Fetching departments - page: ${query.page}, limit: ${query.limit}`);

    const queryBuilder = this.departmentRepository.query().orderBy('created_at', 'DESC');

    return await paginate(queryBuilder, query.page, query.limit);
  }

  /**
   * Find department by ID
   * @param id - Department ID
   * @returns Promise<Department>
   */
  async findById(id: string): Promise<Department> {
    this.logger.debug(`Finding department by ID: ${id}`);

    const department = await this.departmentRepository.query().findById(id);

    if (!department) {
      throw new NotFoundException(`Department with ID ${id} not found`);
    }

    return department;
  }

  /**
   * Find department by code
   * @param code - Department code
   * @returns Promise<Department>
   */
  async findByCode(code: string): Promise<Department> {
    this.logger.debug(`Finding department by code: ${code}`);

    const department = await this.departmentRepository.query().findOne({
      code,
    });

    if (!department) {
      throw new NotFoundException(`Department with code ${code} not found`);
    }

    return department;
  }

  /**
   * Update department
   * @param id - Department ID
   * @param dto - Update department data
   * @returns Promise<Department>
   */
  async update(id: string, dto: UpdateDepartmentDto): Promise<Department> {
    this.logger.debug(`Updating department: ${id}`);

    const department = await this.findById(id);

    // Check if code is being changed and if it already exists
    if (dto.code && dto.code !== department.code) {
      const existingDepartment = await this.departmentRepository.query().findOne({
        code: dto.code,
      });

      if (existingDepartment) {
        throw new ConflictException(`Department with code ${dto.code} already exists`);
      }
    }

    const updatedDepartment = await department.$query().patchAndFetch(dto as any);

    this.logger.log(`Department updated successfully: ${id}`);
    return updatedDepartment;
  }

  /**
   * Regenerate API key for a department
   * @param id - Department ID
   * @returns Promise containing new API key pair
   */
  async regenerateApiKey(id: string): Promise<{ apiKey: string; apiSecret: string }> {
    this.logger.debug(`Regenerating API key for department: ${id}`);

    const department = await this.findById(id);
    const { apiKey, apiSecret, apiKeyHash, apiSecretHash } = await generateApiKey();

    await department.$query().patch({
      apiKeyHash,
      apiSecretHash,
    });

    this.logger.log(`API key regenerated for department: ${id}`);
    return { apiKey, apiSecret };
  }

  /**
   * Update webhook configuration
   * @param id - Department ID
   * @param webhookUrl - Webhook URL
   * @param webhookSecret - Webhook secret for HMAC verification
   * @returns Promise<Department>
   */
  async updateWebhook(id: string, webhookUrl: string, webhookSecret?: string): Promise<Department> {
    this.logger.debug(`Updating webhook for department: ${id}`);

    const department = await this.findById(id);

    const updatedDepartment = await department.$query().patchAndFetch({
      webhookUrl: webhookUrl,
      webhookSecretHash: webhookSecret || crypto.randomBytes(32).toString('hex'),
    });
    this.logger.log(`Webhook updated for department: ${id}`);

    return updatedDepartment;
  }

  /**
   * Get statistics for a department
   * @param code - Department code
   * @param startDate - Start date for statistics
   * @param endDate - End date for statistics
   * @returns Promise<DepartmentStatsDto>
   */
  async getStats(code: string, startDate?: Date, endDate?: Date): Promise<DepartmentStatsDto> {
    this.logger.debug(
      `Fetching statistics for department: ${code} from ${startDate} to ${endDate}`,
    );

    const department = await this.findByCode(code);

    // TODO: Implement stats aggregation using Objection.js
    const stats: DepartmentStatsDto = {
      departmentCode: department.code,
      departmentName: department.name,
      totalApplicants: 0,
      totalCredentialsIssued: 0,
      isActive: department.isActive,
      createdAt: department.createdAt,
      updatedAt: department.updatedAt,
    };

    this.logger.debug(`Retrieved statistics for department: ${code}`);
    return stats;
  }

  /**
   * Deactivate a department
   * @param id - Department ID
   * @returns Promise<void>
   */
  async deactivate(id: string): Promise<void> {
    this.logger.debug(`Deactivating department: ${id}`);

    const department = await this.findById(id);
    await department.$query().patch({ isActive: false });
    this.logger.log(`Department deactivated: ${id}`);
  }

  /**
   * Activate a department
   * @param id - Department ID
   * @returns Promise<Department>
   */
  async activate(id: string): Promise<Department> {
    this.logger.debug(`Activating department: ${id}`);

    const department = await this.findById(id);
    const updatedDepartment = await department.$query().patchAndFetch({ isActive: true });
    this.logger.log(`Department activated: ${id}`);

    return updatedDepartment;
  }

  /**
   * Count total departments
   * @returns Promise<number>
   */
  async countAll(): Promise<number> {
    return await this.departmentRepository.query().resultSize();
  }

  /**
   * Count active departments
   * @returns Promise<number>
   */
  async countActive(): Promise<number> {
    return await this.departmentRepository.query().where({ isActive: true }).resultSize();
  }
}
