import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ElasticsearchService } from '@nestjs/elasticsearch';
import { ConfigService } from '@nestjs/config';
import { SearchQueryDto } from './dto/search-query.dto';

@Injectable()
export class SearchService implements OnModuleInit {
  private readonly logger = new Logger(SearchService.name);
  private readonly indexName = 'dms_documents';
  private isElasticsearchAvailable = false;

  constructor(
    private readonly esService: ElasticsearchService,
    private readonly configService: ConfigService
  ) {}

  async onModuleInit() {
    // Test Elasticsearch connection first
    try {
      await this.esService.ping();
      this.logger.log('Elasticsearch connection successful');
      this.isElasticsearchAvailable = true;
    } catch (error) {
      this.logger.error(
        `Elasticsearch connection failed: ${(error as Error).message}`,
        (error as Error).stack
      );
      this.isElasticsearchAvailable = false;
      return; // Don't try to create index if connection fails
    }

    await this.createIndexIfNotExists();
  }

  /**
   * สร้าง Index และกำหนด Mapping (Schema)
   */
  private async createIndexIfNotExists() {
    try {
      const indexExists = await this.esService.indices.exists({
        index: this.indexName,
      });

      if (!indexExists) {
        // ✅ FIX: Cast 'body' เป็น any เพื่อแก้ปัญหา Type Mismatch ของ Library
        await this.esService.indices.create({
          index: this.indexName,
          body: {
            mappings: {
              properties: {
                id: { type: 'integer' },
                uuid: { type: 'keyword' }, // ADR-019: public identifier
                type: { type: 'keyword' }, // correspondence, rfa, drawing
                docNumber: { type: 'text' },
                title: { type: 'text', analyzer: 'standard' },
                description: { type: 'text', analyzer: 'standard' },
                status: { type: 'keyword' },
                projectId: { type: 'integer' },
                createdAt: { type: 'date' },
                tags: { type: 'text' },
              },
            },
          } as unknown as Record<string, unknown>,
        });
        this.logger.log(`Elasticsearch index '${this.indexName}' created.`);
      }
    } catch (error) {
      this.logger.error(`Failed to create index: ${(error as Error).message}`);
    }
  }

  /**
   * Index เอกสาร (Create/Update)
   */
  async indexDocument(
    doc: Record<string, unknown> & {
      type: string;
      id?: number;
      publicId?: string;
    }
  ) {
    try {
      return await this.esService.index({
        index: this.indexName,
        id: doc.publicId
          ? `${doc.type}_${doc.publicId}`
          : `${doc.type}_${doc.id}`, // ADR-019: prefer publicId key
        document: doc, // ✅ Library รุ่นใหม่ใช้ 'document' แทน 'body' ในบางเวอร์ชัน
      });
    } catch (error) {
      this.logger.error(
        `Failed to index document: ${(error as Error).message}`
      );
    }
  }

  /**
   * ลบเอกสารออกจาก Index
   */
  async removeDocument(type: string, id: number) {
    try {
      await this.esService.delete({
        index: this.indexName,
        id: `${type}_${id}`,
      });
    } catch (error) {
      this.logger.error(
        `Failed to remove document: ${(error as Error).message}`
      );
    }
  }

  /**
   * ค้นหาเอกสาร (Full-text Search)
   */
  async search(queryDto: SearchQueryDto) {
    const { q, type, status, projectId, page = 1, limit = 20 } = queryDto;
    const from = (page - 1) * limit;

    // Early fallback if Elasticsearch is not available
    if (!this.isElasticsearchAvailable) {
      this.logger.warn('Search unavailable - Elasticsearch not connected');
      return { data: [], meta: { total: 0, page, limit, took: 0 } };
    }

    const mustQueries: Record<string, unknown>[] = [];

    // 1. Full-text search logic
    if (q) {
      mustQueries.push({
        multi_match: {
          query: q,
          fields: ['title^3', 'docNumber^2', 'description', 'tags'], // Boost ความสำคัญ
          fuzziness: 'AUTO',
        },
      });
    } else {
      mustQueries.push({ match_all: {} });
    }

    // 2. Filter logic
    const filterQueries: Record<string, unknown>[] = [];
    if (type) filterQueries.push({ term: { type } });
    if (status) filterQueries.push({ term: { status } });
    if (projectId) filterQueries.push({ term: { projectId } });

    try {
      const result = await this.esService.search({
        index: this.indexName,
        from,
        size: limit,
        // ✅ ส่ง Query Structure โดยตรง
        query: {
          bool: {
            must: mustQueries,
            filter: filterQueries,
          },
        },
        sort: [{ createdAt: { order: 'desc' as const } }],
      });

      // 3. Format Result
      const hits = result.hits.hits;
      const total =
        typeof result.hits.total === 'number'
          ? result.hits.total
          : result.hits.total?.value || 0;

      return {
        data: hits.map((hit) => hit._source),
        meta: {
          total,
          page,
          limit,
          took: result.took,
        },
      };
    } catch (error) {
      const err = error as Error;
      this.logger.error(`Search failed: ${err.message}`, err.stack);
      this.logger.debug(
        `Search query context: ${JSON.stringify({
          query: queryDto,
          esNode: String(this.configService.get('ELASTICSEARCH_NODE') ?? ''),
        })}`
      );
      return { data: [], meta: { total: 0, page, limit, took: 0 } };
    }
  }
}
