import {
  Injectable,
  NotFoundException,
  BadRequestException,
  Inject,
  forwardRef,
} from '@nestjs/common';
import { QueryRepository } from './repositories/query.repository';
import { CreateQueryDto, UpdateQueryDto } from './dto';
import { QueryStage } from '../../entities/query.entity';
import { AuditService } from '../audit/audit.service';
import { QuoteService } from './quote.service';
import { serviceMessages } from '../../common/constants/messages';

const MSG = serviceMessages('Query');
const ENTITY_TYPE = 'query';

@Injectable()
export class QueryService {
  constructor(
    private readonly repo: QueryRepository,
    private readonly auditService: AuditService,
    @Inject(forwardRef(() => QuoteService))
    private readonly quoteService: QuoteService,
  ) {}

  async findAll(
    query?: { page?: number; limit?: number; search?: string },
    filters?: { stage?: string; destinationId?: string; sourceId?: string; assignedTo?: string },
  ) {
    const where: any = {};
    if (filters?.stage) where.stage = filters.stage;
    if (filters?.destinationId) where.destination_id = filters.destinationId;
    if (filters?.sourceId) where.source_id = filters.sourceId;

    const result = await this.repo.findPaginated({
      page: query?.page,
      limit: query?.limit,
      search: query?.search,
      searchColumns: ['query_number', 'guest_name', 'guest_mobile', 'guest_email'],
      relations: ['destination', 'source', 'quotes'],
      order: { created_at: 'DESC' },
      where,
    });

    // Resolve user names for sales_team and marketing_leads
    await this.resolveUserNames(result.items);

    // Filter by assignedTo after fetch (JSON array field)
    if (filters?.assignedTo) {
      result.items = result.items.filter((q: any) =>
        Array.isArray(q.sales_team) && q.sales_team.includes(filters.assignedTo),
      );
    }

    return result;
  }

  async findAvailable() {
    return this.repo.findAvailable();
  }

  async findById(id: string) {
    const item = await this.repo.findByIdWithRelations(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);
    await this.resolveUserNames([item]);
    return item;
  }

  /**
   * Resolve user names for sales_team UUID arrays.
   * Attaches sales_team_users to each query.
   */
  private async resolveUserNames(queries: any[]) {
    const allIds = new Set<string>();
    for (const q of queries) {
      (q.sales_team || []).forEach((id: string) => allIds.add(id));
    }
    if (allIds.size === 0) {
      for (const q of queries) {
        q.sales_team_users = [];
      }
      return;
    }
    const userRows = await (this.repo as any).repo.manager.query(
      `SELECT id, name FROM users WHERE id = ANY($1) AND is_deleted = false`,
      [Array.from(allIds)],
    );
    const userMap = new Map(userRows.map((u: any) => [u.id, u.name]));
    for (const q of queries) {
      q.sales_team_users = (q.sales_team || [])
        .filter((id: string) => userMap.has(id))
        .map((id: string) => ({ id, name: userMap.get(id) }));
    }
  }

  async getStageCounts() {
    return this.repo.countByStage();
  }

  async create(dto: CreateQueryDto) {
    const queryNumber = await this.repo.getNextQueryNumber();

    const item = await this.repo.save({
      query_number: queryNumber,
      source_id: dto.source_id,
      ref_id: dto.ref_id || null,
      sales_team: dto.sales_team,
      source_tags: dto.source_tags || null,
      remark_tags: dto.remark_tags || null,
      destination_id: dto.destination_id,
      start_date: dto.start_date,
      end_date: dto.end_date,
      adults: dto.adults,
      children_ages: dto.children_ages || null,
      infants: dto.infants ?? 0,
      guest_name: dto.guest_name,
      guest_email: dto.guest_email || null,
      guest_mobile: dto.guest_mobile,
      guest_alt_phone: dto.guest_alt_phone || null,
      notes: dto.notes || null,
      stage: dto.stage || QueryStage.NEW,
    });

    await this.auditService.log(ENTITY_TYPE, item.id, 'created');

    return { id: item.id, query_number: queryNumber, message: MSG.CREATED };
  }

  async update(id: string, dto: UpdateQueryDto) {
    const item = await this.repo.findByIdWithRelations(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);

    const updateData: any = {};
    const fields = [
      'source_id', 'ref_id', 'sales_team', 'source_tags', 'remark_tags',
      'destination_id', 'start_date', 'end_date',
      'adults', 'children_ages', 'infants',
      'guest_name', 'guest_email', 'guest_mobile', 'guest_alt_phone',
      'notes', 'stage',
    ];
    for (const field of fields) {
      if ((dto as any)[field] !== undefined) updateData[field] = (dto as any)[field];
    }

    // Build change log with human-readable values for reference fields
    const changes: { field: string; old_value: any; new_value: any }[] = [];
    for (const field of Object.keys(updateData)) {
      const oldVal = (item as any)[field];
      const newVal = updateData[field];
      if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
        if (field === 'destination_id') {
          changes.push({ field, old_value: item.destination?.name || null, new_value: newVal });
        } else if (field === 'source_id') {
          changes.push({ field, old_value: item.source?.name || null, new_value: newVal });
        } else {
          changes.push({ field, old_value: oldVal ?? null, new_value: newVal ?? null });
        }
      }
    }

    await this.repo.update(id, updateData);

    // Sync basic details to linked bookings (first sales_team member → assigned_to)
    const syncFields = ['guest_name', 'guest_mobile', 'guest_email', 'guest_alt_phone', 'start_date', 'end_date', 'adults', 'children_ages', 'infants'];
    const bookingSyncData: any = {};
    for (const field of syncFields) {
      if (updateData[field] !== undefined) bookingSyncData[field] = updateData[field];
    }
    if (updateData.sales_team?.length > 0) {
      bookingSyncData.assigned_to = updateData.sales_team[0];
    }
    if (Object.keys(bookingSyncData).length > 0) {
      const tenantId = (this.repo as any).getTenantId();
      await (this.repo as any).repo.manager.update('bookings', { query_id: id, tenant_id: tenantId, is_deleted: false }, bookingSyncData);
    }

    if (changes.length > 0) {
      await this.auditService.log(ENTITY_TYPE, id, 'updated', changes);
    }

    return { id, message: MSG.UPDATED };
  }

  async changeStage(id: string, stage: QueryStage) {
    const item = await this.repo.findById(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);

    const validTransitions: Record<string, string[]> = {
      [QueryStage.NEW]: [QueryStage.IN_PROGRESS, QueryStage.CANCELLED, QueryStage.DROPPED],
      [QueryStage.IN_PROGRESS]: [QueryStage.CONVERTED, QueryStage.ON_HOLD, QueryStage.CANCELLED, QueryStage.DROPPED],
      [QueryStage.CONVERTED]: [QueryStage.ON_TRIP, QueryStage.IN_PROGRESS],
      [QueryStage.ON_HOLD]: [QueryStage.IN_PROGRESS, QueryStage.CANCELLED, QueryStage.DROPPED],
      [QueryStage.ON_TRIP]: [QueryStage.PAST_TRIP],
      [QueryStage.PAST_TRIP]: [],
      [QueryStage.CANCELLED]: [QueryStage.NEW],
      [QueryStage.DROPPED]: [QueryStage.NEW],
    };

    const allowed = validTransitions[item.stage] || [];
    if (!allowed.includes(stage)) {
      throw new BadRequestException(`Cannot change stage from "${item.stage}" to "${stage}"`);
    }

    await this.repo.update(id, { stage });

    await this.auditService.log(ENTITY_TYPE, id, 'stage_changed', [
      { field: 'stage', old_value: item.stage, new_value: stage },
    ]);

    return { id, stage, message: `Stage changed to ${stage}` };
  }

  async duplicate(id: string) {
    const original = await this.repo.findById(id);
    if (!original) throw new NotFoundException(MSG.NOT_FOUND);

    const newQuery = await this.repo.save({
      source_id: original.source_id,
      ref_id: original.ref_id ? `${original.ref_id}-copy` : null,
      sales_team: original.sales_team || [],
      source_tags: original.source_tags || null,
      destination_id: original.destination_id,
      start_date: original.start_date,
      end_date: original.end_date,
      adults: original.adults,
      children_ages: original.children_ages,
      infants: original.infants,
      guest_name: original.guest_name,
      guest_mobile: original.guest_mobile,
      guest_email: original.guest_email,
      guest_alt_phone: original.guest_alt_phone,
      notes: original.notes ? `Duplicated from ${(original as any).query_number || id}. ${original.notes}` : `Duplicated from ${(original as any).query_number || id}`,
      remark_tags: original.remark_tags,
      stage: QueryStage.NEW,
      enabled_services: (original as any).enabled_services,
    } as any);

    // Duplicate all quotes from original query
    const originalQuotes = await this.quoteService.findByQueryId(id);
    for (const quote of originalQuotes) {
      await this.quoteService.duplicateQuote(newQuery.id, quote.id);
    }

    await this.auditService.log(ENTITY_TYPE, newQuery.id, 'created');
    return { id: newQuery.id, query_number: (newQuery as any).query_number, message: 'Query duplicated with all quotes' };
  }

  async remove(id: string) {
    const item = await this.repo.findById(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);

    await this.repo.softDelete(id);
    await this.auditService.log(ENTITY_TYPE, id, 'deleted');
    return { message: MSG.DELETED };
  }
}
