import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import { QuoteRepository } from './repositories/quote.repository';
import { CreateQuoteDto } from './dto/create-quote.dto';
import { UpdateQuoteDto } from './dto/update-quote.dto';
import { QueryQuoteHotelEntity } from '../../entities/query-quote-hotel.entity';
import { QueryQuoteTransportEntity } from '../../entities/query-quote-transport.entity';
import { QueryQuoteActivityEntity } from '../../entities/query-quote-activity.entity';
import { QueryQuoteSpecialEntity } from '../../entities/query-quote-special.entity';
import { TransportPricingEntity } from '../../entities/transport-pricing.entity';
import { ActivityPricingEntity } from '../../entities/activity-pricing.entity';
import { SeasonEntity } from '../../entities/season.entity';
import { ExchangeRateEntity } from '../../entities/exchange-rate.entity';
import { AuditService } from '../audit/audit.service';
import { QueryEntity, QueryStage } from '../../entities/query.entity';

const ENTITY_TYPE = 'query';

@Injectable()
export class QuoteService {
  constructor(
    private readonly repo: QuoteRepository,
    private readonly cls: ClsService,
    private readonly auditService: AuditService,
    @InjectRepository(QueryQuoteHotelEntity) private readonly hotelItemRepo: Repository<QueryQuoteHotelEntity>,
    @InjectRepository(QueryQuoteTransportEntity) private readonly transportItemRepo: Repository<QueryQuoteTransportEntity>,
    @InjectRepository(QueryQuoteActivityEntity) private readonly activityItemRepo: Repository<QueryQuoteActivityEntity>,
    @InjectRepository(QueryQuoteSpecialEntity) private readonly specialItemRepo: Repository<QueryQuoteSpecialEntity>,
    @InjectRepository(TransportPricingEntity) private readonly transportPricingRepo: Repository<TransportPricingEntity>,
    @InjectRepository(ActivityPricingEntity) private readonly activityPricingRepo: Repository<ActivityPricingEntity>,
    @InjectRepository(SeasonEntity) private readonly seasonRepo: Repository<SeasonEntity>,
    @InjectRepository(ExchangeRateEntity) private readonly exchangeRateRepo: Repository<ExchangeRateEntity>,
    @InjectRepository(QueryEntity) private readonly queryRepo: Repository<QueryEntity>,
  ) {}

  private getTenantId(): string {
    return this.cls.get('tenant_id');
  }

  async findByQueryId(queryId: string) {
    return this.repo.findByQueryId(queryId);
  }

  async findSummaries(queryId: string) {
    return this.repo.findSummariesByQueryId(queryId);
  }

  async findQuoteDetail(queryId: string, quoteId: string) {
    const quote = await this.repo.findByIdWithRelations(quoteId);
    if (!quote || quote.query_id !== queryId || quote.is_deleted) throw new NotFoundException('Quote not found');

    // Filter soft-deleted items + project nested entities to trim verbose fields
    if (quote.hotel_items) {
      quote.hotel_items = quote.hotel_items.filter(i => !i.is_deleted);
      for (const hi of quote.hotel_items) {
        if (hi.hotel) {
          hi.hotel = { id: hi.hotel.id, name: hi.hotel.name, destination_id: hi.hotel.destination_id } as any;
        }
      }
    }
    if (quote.transport_items) {
      quote.transport_items = quote.transport_items.filter(i => !i.is_deleted);
      for (const ti of quote.transport_items) {
        if (ti.transport_service) {
          ti.transport_service = { id: ti.transport_service.id, from_city: ti.transport_service.from_city, to_city: ti.transport_service.to_city, category: ti.transport_service.category, short_code: ti.transport_service.short_code, destination_id: ti.transport_service.destination_id } as any;
        }
      }
    }
    if (quote.activity_items) {
      quote.activity_items = quote.activity_items.filter(i => !i.is_deleted);
      for (const ai of quote.activity_items) {
        if (ai.activity) {
          ai.activity = { id: ai.activity.id, name: ai.activity.name, destination_id: ai.activity.destination_id } as any;
        }
        if (ai.ticket) {
          ai.ticket = { id: ai.ticket.id, name: ai.ticket.name, duration_mins: ai.ticket.duration_mins } as any;
        }
      }
    }
    if (quote.special_items) quote.special_items = quote.special_items.filter(i => !i.is_deleted);

    return quote;
  }

  async createQuote(queryId: string, dto: CreateQuoteDto) {
    const tenantId = this.getTenantId();
    const userId = this.cls.get('user_id') || null;
    const quoteNumber = await this.repo.getNextQuoteNumber(queryId);

    const quote = await this.repo.save({
      query_id: queryId,
      quote_number: quoteNumber,
      label: dto.label || `Quote ${quoteNumber}`,
      is_selected: dto.is_selected ?? false,
      notes: dto.notes || null,
      pricing_strategy: dto.pricing_strategy || 'overall',
      selling_currency_id: dto.selling_currency_id || null,
      exchange_rate: dto.exchange_rate ?? 1,
      markup_type: dto.markup_type || 'amount',
      rounding: dto.rounding ?? 1,
      markup_amount: dto.markup_amount ?? 0,
      hotel_markup: dto.hotel_markup ?? 0,
      transport_markup: dto.transport_markup ?? 0,
      activity_markup: dto.activity_markup ?? 0,
      special_markup: dto.special_markup ?? 0,
      transport_excluded_pax: dto.transport_excluded_pax ?? 0,
      selling_total: dto.selling_total ?? 0,
      internal_comments: dto.internal_comments || null,
      customer_remarks: dto.customer_remarks || null,
    } as any);

    await this.saveChildItems(quote.id, tenantId, userId, dto);
    await this.recalculateTotals(quote.id, tenantId);

    // Auto-transition query from New → In Progress
    const query = await this.queryRepo.findOne({ where: { id: queryId, tenant_id: tenantId } as any });
    if (query && query.stage === QueryStage.NEW) {
      await this.queryRepo.update(queryId, { stage: QueryStage.IN_PROGRESS } as any);
      await this.auditService.log(ENTITY_TYPE, queryId, 'stage_changed', [
        { field: 'stage', old_value: 'New', new_value: 'In Progress' },
      ]);
    }

    await this.auditService.log(ENTITY_TYPE, queryId, 'updated', [
      { field: 'quotes', old_value: null, new_value: `Added Quote ${quoteNumber}` },
    ]);

    return { id: quote.id, quote_number: quoteNumber, message: 'Quote created successfully' };
  }

  async updateQuote(queryId: string, quoteId: string, dto: UpdateQuoteDto) {
    const quote = await this.repo.findById(quoteId);
    if (!quote || quote.query_id !== queryId) throw new NotFoundException('Quote not found');

    const tenantId = this.getTenantId();
    const userId = this.cls.get('user_id') || null;

    // Update quote fields
    const updateData: any = {};
    const fields = [
      'label', 'is_selected', 'notes', 'pricing_strategy', 'selling_currency_id',
      'exchange_rate', 'markup_type', 'rounding', 'markup_amount',
      'hotel_markup', 'transport_markup', 'activity_markup', 'special_markup',
      'transport_excluded_pax', 'selling_total', 'internal_comments', 'customer_remarks',
    ];
    for (const field of fields) {
      if ((dto as any)[field] !== undefined) updateData[field] = (dto as any)[field];
    }
    if (Object.keys(updateData).length > 0) await this.repo.update(quoteId, updateData);

    // Replace child items if provided
    const hasItems = dto.hotel_items || dto.transport_items || dto.activity_items || dto.special_items;
    if (hasItems) {
      await this.hardDeleteChildItems(quoteId, tenantId);
      await this.saveChildItems(quoteId, tenantId, userId, dto);
      await this.recalculateTotals(quoteId, tenantId);
    }

    await this.auditService.log(ENTITY_TYPE, queryId, 'updated', [
      { field: 'quotes', old_value: null, new_value: 'Updated itinerary details' },
    ]);

    return { id: quoteId, message: 'Quote updated successfully' };
  }

  async deleteQuote(queryId: string, quoteId: string) {
    const quote = await this.repo.findById(quoteId);
    if (!quote || quote.query_id !== queryId) throw new NotFoundException('Quote not found');

    // Ensure at least one quote remains
    const count = await this.repo.count({
      where: { query_id: queryId } as any,
    });
    if (count <= 1) throw new BadRequestException('Cannot delete the last quote');

    const tenantId = this.getTenantId();
    await this.hardDeleteChildItems(quoteId, tenantId);
    await this.repo.softDelete(quoteId);

    await this.auditService.log(ENTITY_TYPE, queryId, 'updated', [
      { field: 'quotes', old_value: quote.label, new_value: 'Deleted' },
    ]);

    return { message: 'Quote deleted' };
  }

  async selectQuote(queryId: string, quoteId: string) {
    // Unselect all quotes for this query, then select the specified one
    await this.repo.unselectAllForQuery(queryId);
    await this.repo.markSelected(quoteId);

    await this.auditService.log(ENTITY_TYPE, queryId, 'updated', [
      { field: 'selected_quote', old_value: null, new_value: 'Quote selected' },
    ]);

    return { id: queryId, quote_id: quoteId, message: 'Quote selected' };
  }

  async duplicateQuote(queryId: string, quoteId: string) {
    const tenantId = this.getTenantId();
    const original = await this.repo.findByIdWithRelations(quoteId);
    if (!original || original.query_id !== queryId) throw new NotFoundException('Quote not found');

    const nextNumber = await this.repo.getNextQuoteNumber(queryId);
    const userId = this.cls.get('user_id') || null;

    const newQuote = await this.repo.save({
      query_id: queryId,
      quote_number: nextNumber,
      label: `${original.label || 'Quote'} (Copy)`,
      is_selected: false,
      notes: original.notes,
      pricing_strategy: original.pricing_strategy,
      selling_currency_id: original.selling_currency_id,
      exchange_rate: original.exchange_rate,
      markup_type: original.markup_type,
      rounding: original.rounding,
      markup_amount: original.markup_amount,
      hotel_markup: original.hotel_markup,
      transport_markup: original.transport_markup,
      activity_markup: original.activity_markup,
      special_markup: original.special_markup,
      transport_excluded_pax: original.transport_excluded_pax,
      selling_total: original.selling_total,
      internal_comments: original.internal_comments,
      customer_remarks: original.customer_remarks,
    } as any);

    // Copy child items
    for (const hi of (original.hotel_items || []).filter(i => !i.is_deleted)) {
      await this.hotelItemRepo.save({ ...this.stripMeta(hi), quote_id: newQuote.id, tenant_id: tenantId, created_by: userId, updated_by: userId });
    }
    for (const ti of (original.transport_items || []).filter(i => !i.is_deleted)) {
      await this.transportItemRepo.save({ ...this.stripMeta(ti), quote_id: newQuote.id, tenant_id: tenantId, created_by: userId, updated_by: userId });
    }
    for (const ai of (original.activity_items || []).filter(i => !i.is_deleted)) {
      await this.activityItemRepo.save({ ...this.stripMeta(ai), quote_id: newQuote.id, tenant_id: tenantId, created_by: userId, updated_by: userId });
    }
    for (const si of (original.special_items || []).filter(i => !i.is_deleted)) {
      await this.specialItemRepo.save({ ...this.stripMeta(si), quote_id: newQuote.id, tenant_id: tenantId, created_by: userId, updated_by: userId });
    }

    await this.recalculateTotals(newQuote.id, tenantId);

    return { id: queryId, quote_id: newQuote.id, quote_number: nextNumber, message: 'Quote duplicated' };
  }

  // --- Price Lookups ---
  async lookupTransportPrice(transportServiceId: string, vehicleType: string, date?: string) {
    const tenantId = this.getTenantId();
    // Get destination currency from the transport service
    const ts = await this.transportPricingRepo.manager.findOne('transport_services', { where: { id: transportServiceId } });
    const dest = ts ? await this.transportPricingRepo.manager.findOne('destinations', { where: { id: (ts as any).destination_id } }) : null;
    const destCurrencyId = (dest as any)?.currency_id;

    const where: any = { transport_service_id: transportServiceId, cab_type: vehicleType, tenant_id: tenantId, is_deleted: false };
    if (destCurrencyId) where.currency_id = destCurrencyId;

    const pricings = await this.transportPricingRepo.find({ where });
    // Prefer pricing row with any price set (default or seasonal)
    const hasAnyPrice = (p: any) => p.default_price != null || p.season_1_price != null || p.season_2_price != null || p.season_3_price != null || p.season_4_price != null || p.season_5_price != null;
    const pricing = pricings.find(hasAnyPrice) || pricings[0] || null;
    if (!pricing) return { price: null, source: 'not_found' };

    if (date) {
      const result = await this.getSeasonalPrice(pricing, date, tenantId, pricing.destination_id);
      if (result !== null) return { price: result.price, source: 'seasonal', season_name: result.season_name };
    }
    return { price: pricing.default_price, source: 'default', season_name: null };
  }

  async lookupActivityPrice(activityId: string, ticketId: string | null, ageGroup: string, date?: string) {
    const tenantId = this.getTenantId();
    // Get destination currency from the activity
    const act = await this.activityPricingRepo.manager.findOne('activities', { where: { id: activityId } });
    const dest = act ? await this.activityPricingRepo.manager.findOne('destinations', { where: { id: (act as any).destination_id } }) : null;
    const destCurrencyId = (dest as any)?.currency_id;

    const where: any = { activity_id: activityId, age_group: ageGroup, tenant_id: tenantId, is_deleted: false };
    if (ticketId) where.ticket_id = ticketId;
    if (destCurrencyId) where.currency_id = destCurrencyId;

    const pricings = await this.activityPricingRepo.find({ where });
    const hasAnyPrice = (p: any) => p.default_price != null || p.season_1_price != null || p.season_2_price != null || p.season_3_price != null || p.season_4_price != null || p.season_5_price != null;
    const pricing = pricings.find(hasAnyPrice) || pricings[0] || null;
    if (!pricing) return { price: null, source: 'not_found', season_name: null };

    if (date) {
      const result = await this.getSeasonalPrice(pricing, date, tenantId, pricing.destination_id);
      if (result !== null) return { price: result.price, source: 'seasonal', season_name: result.season_name };
    }
    return { price: pricing.default_price, source: 'default', season_name: null };
  }

  async lookupExchangeRate(fromCurrencyId: string, toCurrencyId: string) {
    if (fromCurrencyId === toCurrencyId) return { rate: 1 };
    const tenantId = this.getTenantId();
    const rate = await this.exchangeRateRepo.findOne({
      where: { from_currency_id: fromCurrencyId, to_currency_id: toCurrencyId, tenant_id: tenantId, is_deleted: false, is_active: true } as any,
    });
    return { rate: rate ? Number(rate.rate) : null };
  }

  // --- Private Helpers ---
  private async saveChildItems(quoteId: string, tenantId: string, userId: string | null, dto: any) {
    if (dto.hotel_items?.length) {
      for (const hi of dto.hotel_items) {
        await this.hotelItemRepo.save({
          ...hi, quote_id: quoteId, tenant_id: tenantId,
          pax_per_room: hi.pax_per_room ?? 1, num_rooms: hi.num_rooms ?? 1,
          aweb: hi.aweb ?? 0, cweb: hi.cweb ?? 0, cnb: hi.cnb ?? 0,
          subtotal: hi.subtotal ?? 0, created_by: userId, updated_by: userId,
        });
      }
    }
    if (dto.transport_items?.length) {
      for (const ti of dto.transport_items) {
        const qty = ti.quantity ?? 1;
        const price = ti.unit_price ?? 0;
        await this.transportItemRepo.save({
          ...ti, quote_id: quoteId, tenant_id: tenantId,
          quantity: qty, unit_price: price, subtotal: ti.subtotal ?? (qty * price),
          created_by: userId, updated_by: userId,
        });
      }
    }
    if (dto.activity_items?.length) {
      for (const ai of dto.activity_items) {
        const qty = ai.quantity ?? 1;
        const price = ai.unit_price ?? 0;
        await this.activityItemRepo.save({
          ...ai, quote_id: quoteId, tenant_id: tenantId,
          quantity: qty, unit_price: price, subtotal: ai.subtotal ?? (qty * price),
          ticket_id: ai.ticket_id || null,
          created_by: userId, updated_by: userId,
        });
      }
    }
    if (dto.special_items?.length) {
      for (const si of dto.special_items) {
        await this.specialItemRepo.save({
          ...si, quote_id: quoteId, tenant_id: tenantId,
          created_by: userId, updated_by: userId,
        });
      }
    }
  }

  private async hardDeleteChildItems(quoteId: string, tenantId: string) {
    for (const repo of [this.hotelItemRepo, this.transportItemRepo, this.activityItemRepo, this.specialItemRepo]) {
      await repo.delete({ quote_id: quoteId, tenant_id: tenantId } as any);
    }
  }

  private async recalculateTotals(quoteId: string, tenantId: string) {
    const hotelTotal = await this.sumField(this.hotelItemRepo, 'quote_id', quoteId, tenantId, 'subtotal');
    const transportTotal = await this.sumField(this.transportItemRepo, 'quote_id', quoteId, tenantId, 'subtotal');
    const activityTotal = await this.sumField(this.activityItemRepo, 'quote_id', quoteId, tenantId, 'subtotal');
    const specialTotal = await this.sumField(this.specialItemRepo, 'quote_id', quoteId, tenantId, 'total_price');

    await this.repo.update(quoteId, {
      hotel_total: hotelTotal,
      transport_total: transportTotal,
      activity_total: activityTotal,
      special_total: specialTotal,
      grand_total: hotelTotal + transportTotal + activityTotal + specialTotal,
    });
  }

  private async sumField(repo: Repository<any>, fkColumn: string, fkValue: string, tenantId: string, field: string): Promise<number> {
    const result = await repo
      .createQueryBuilder('item')
      .select(`SUM(item.${field})`, 'total')
      .where(`item.${fkColumn} = :fkValue`, { fkValue })
      .andWhere('item.tenant_id = :tenantId', { tenantId })
      .andWhere('item.is_deleted = false')
      .getRawOne();
    return Number(result?.total) || 0;
  }

  private async getSeasonalPrice(pricing: any, date: string, tenantId: string, destinationId: string): Promise<{ price: number; season_name: string } | null> {
    const seasons = await this.seasonRepo.find({
      where: { destination_id: destinationId, tenant_id: tenantId, is_deleted: false, is_active: true } as any,
      order: { sort_order: 'ASC' },
    });

    // Extract MM-DD from travel date (YYYY-MM-DD → MM-DD)
    const travelMMDD = date.substring(5);

    for (let i = 0; i < seasons.length && i < 5; i++) {
      const season = seasons[i];
      if (!season.start_date || !season.end_date) continue;

      const start = season.start_date; // MM-DD format
      const end = season.end_date;     // MM-DD format
      let match = false;

      if (start <= end) {
        // Normal range (e.g. 03-15 to 09-30)
        match = travelMMDD >= start && travelMMDD <= end;
      } else {
        // Cross-year range (e.g. 12-20 to 01-10)
        match = travelMMDD >= start || travelMMDD <= end;
      }

      if (match) {
        const key = `season_${i + 1}_price`;
        const price = (pricing as any)[key];
        if (price !== null && price !== undefined) return { price: Number(price), season_name: season.name };
      }
    }
    return null;
  }

  private stripMeta(item: any) {
    const { id, created_at, updated_at, created_by, updated_by, is_deleted, quote_id, quote, hotel, transport_service, activity, ticket, ...rest } = item;
    return rest;
  }
}
