import { Injectable, OnModuleInit, RequestMethod } from '@nestjs/common';
import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
import { HAS_FEATURE_KEY } from '../decorators/has-feature.decorator';
import { ResourceService } from '../../resources/resource.service';

const METHOD_MAP = {
  [RequestMethod.GET]: 'GET',
  [RequestMethod.POST]: 'POST',
  [RequestMethod.PUT]: 'PUT',
  [RequestMethod.DELETE]: 'DELETE',
  [RequestMethod.PATCH]: 'PATCH',
};

@Injectable()
export class FeatureScannerService implements OnModuleInit {
  constructor(
    private readonly discoveryService: DiscoveryService,
    private readonly reflector: Reflector,
    private readonly resourceService: ResourceService,
    private readonly metadataScanner: MetadataScanner,
  ) {}

  normalizePath(...parts: string[]) {
    const cleaned = parts
      .filter(Boolean)
      .map((p) => p.replace(/^\/|\/$/g, '')) // bỏ / ở đầu/cuối
      .filter(Boolean); // nếu sau khi clean mà rỗng thì bỏ luôn

    const fullPath = '/' + cleaned.join('/');

    return fullPath === '/' ? fullPath : fullPath.replace(/\/$/, '');
  }

  async scan() {
    const controllers = this.discoveryService.getControllers();

    for (const wrapper of controllers) {
      const { instance } = wrapper;
      if (!instance) continue;

      const controllerClass = instance.constructor;

      // Lấy path và feature của controller
      const controllerPath = Reflect.getMetadata('path', controllerClass) || '';
      const controllerFeature = this.reflector.get(
        HAS_FEATURE_KEY,
        controllerClass,
      );

      // Nếu controller có feature → lưu lại
      if (controllerFeature) {
        await this.saveIfNotExists({
          ...controllerFeature,
          path: controllerPath,
          type: 'controller',
        });
      }

      // Quét các method trong controller
      const prototype = Object.getPrototypeOf(instance);
      const methods = this.metadataScanner.getAllFilteredMethodNames(prototype);

      for (const methodName of methods) {
        const method = prototype[methodName];
        const methodPath = Reflect.getMetadata('path', method) || '';
        const requestMethod = Reflect.getMetadata('method', method);
        const methodFeature = this.reflector.get(HAS_FEATURE_KEY, method);

        if (methodFeature && controllerFeature?.group_key) {
          const fullPath = this.normalizePath(controllerPath, methodPath);

          await this.saveIfNotExists({
            ...methodFeature,
            method_name: methodName,
            method: METHOD_MAP[requestMethod],
            path: fullPath,
            type: 'method',
            group_key: controllerFeature.group_key,
          });
        }
      }
    }
  }

  // Helper để chỉ lưu nếu chưa tồn tại hoặc cập nhật một phần nếu đã có
  private async saveIfNotExists(data: {
    name: string;
    group_key: string;
    type: 'controller' | 'method';
    path?: string;
    method?: string;
    method_name?: string;
    description?: string;
    [key: string]: any;
  }) {
    let existing: any = null;

    if (data.type === 'controller') {
      existing = await this.resourceService.repo.findOne({
        where: {
          name: data.name,
          group_key: data.group_key,
          type: 'controller',
        },
      });
    } else if (data.type === 'method') {
      existing = await this.resourceService.repo.findOne({
        where: {
          path: data.path,
          method: data.method,
          type: 'method',
        },
      });
    }

    if (!existing) {
      // Thêm mới nếu chưa có
      await this.resourceService.repo.save(data);
    } else {
      // Cập nhật các trường cần thiết nếu đã tồn tại
      const updated = {
        ...existing,
        name: data.name ?? existing.name,
        description: data.description ?? existing.description,
        method_name: data.method_name ?? existing.method_name,
        path: data.path,
        group_key: data.group_key,
      };

      await this.resourceService.repo.save(updated);
    }
  }

  async onModuleInit() {
    this.scan();
  }
}
