import { Injectable } from '@nestjs/common';

import {
  AiBookingResult,
  AiContainerType,
  AiDirection,
  AiExtractedBooking,
  AiRecommendation,
  AiValidationResult,
} from './types/ai-booking-result.type';

/**
 * Deterministic keyword/regex "AI" for the booking assistant workflow.
 * No external AI calls — this class is the single seam to swap for a real
 * provider later (OllamaAiService / ClaudeAiService / OpenAiService): keep
 * the `extractBooking(text): AiBookingResult` contract and replace the body.
 */

const KNOWN_LOCATIONS = [
  'Djibouti',
  'Indode',
  'Modjo',
  'Adama',
  'Dire Dawa',
  'Addis Ababa',
] as const;

const INLAND_LOCATIONS = new Set<string>([
  'Indode',
  'Modjo',
  'Adama',
  'Dire Dawa',
  'Addis Ababa',
]);

// Longest names first so "Dire Dawa" wins before a shorter partial could.
const LOCATION_ALTERNATION = [...KNOWN_LOCATIONS]
  .sort((a, b) => b.length - a.length)
  .map((name) => name.replace(/\s+/g, '\\s+'))
  .join('|');

// Checked in order; first hit wins, so specific cargo words beat the
// generic "refrigerated" fallback.
const CARGO_KEYWORDS: ReadonlyArray<readonly [RegExp, string]> = [
  [/\belectronics\b/i, 'electronics'],
  [/\bcoffee\b/i, 'coffee'],
  [/\bwheat\b/i, 'wheat'],
  [/\bfertilizers?\b/i, 'fertilizer'],
  [/\bchemicals?\b/i, 'chemical'],
  [/\bmachinery\b/i, 'machinery'],
  [/\bmedicines?\b/i, 'medicine'],
  [/\bsesame\b/i, 'sesame'],
  [/\b(?:vehicles?|cars?)\b/i, 'vehicles'],
  [/\brefrigerated\b/i, 'refrigerated cargo'],
];

const WORD_NUMBERS: Record<string, number> = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7,
  eight: 8,
  nine: 9,
  ten: 10,
};

// A capitalized-word run: "ABC Logistics", "Auto Import PLC", "Ethio Coffee
// Export". Stops at the first lowercase word ("wants", "needs", …).
const NAME_CAPTURE = String.raw`([A-Z][A-Za-z0-9&.'-]*(?:\s+[A-Z][A-Za-z0-9&.'-]*)*)`;

// No `i` flag: the capture relies on case ([A-Z] word starts) to know where
// the company name ends ("Customer ABC Logistics wants…" → "ABC Logistics").
const CUSTOMER_PATTERNS: ReadonlyArray<RegExp> = [
  new RegExp(String.raw`\b[Cc]ustomer(?:\s+is)?\s*:?\s+${NAME_CAPTURE}`),
  new RegExp(String.raw`\b[Ff]or\s+${NAME_CAPTURE}`),
];

const RECOMMEND_CREATE: AiRecommendation = {
  action: 'CREATE_DRAFT_BOOKING',
  message:
    'Booking data looks complete. User can review and create a draft booking.',
  confidence: 0.85,
};

const RECOMMEND_MISSING: AiRecommendation = {
  action: 'REQUEST_MISSING_INFORMATION',
  message:
    'Some required booking information is missing. Ask the customer for the missing fields before creating a draft booking.',
  confidence: 0.45,
};

@Injectable()
export class MockAiService {
  extractBooking(text: string): AiBookingResult {
    const input = text.trim();

    const { origin, destination } = this.extractRoute(input);

    const extracted: AiExtractedBooking = {
      customerName: this.extractCustomerName(input),
      origin,
      destination,
      cargoType: this.extractCargoType(input),
      containerType: this.extractContainerType(input),
      quantity: this.extractQuantity(input),
      direction: this.resolveDirection(origin, destination),
      weightKg: this.extractWeightKg(input),
      pickupRequired: this.extractFlag(input, 'pickup'),
      deliveryRequired: this.extractFlag(input, 'delivery'),
    };

    const validation = this.validate(extracted);

    return {
      provider: 'mock',
      extracted,
      validation,
      recommendation: validation.valid ? RECOMMEND_CREATE : RECOMMEND_MISSING,
    };
  }

  private extractCustomerName(text: string): string | null {
    for (const pattern of CUSTOMER_PATTERNS) {
      const match = text.match(pattern);
      if (match?.[1]) {
        const name = match[1].replace(/[.,;:!?]+$/, '').trim();
        if (name) return name;
      }
    }
    return null;
  }

  private extractRoute(text: string): {
    origin: string | null;
    destination: string | null;
  } {
    const fromMatch = text.match(
      new RegExp(String.raw`\bfrom\s+(${LOCATION_ALTERNATION})\b`, 'i'),
    );
    const toMatch = text.match(
      new RegExp(String.raw`\bto\s+(${LOCATION_ALTERNATION})\b`, 'i'),
    );

    let origin = fromMatch ? this.canonicalLocation(fromMatch[1]) : null;
    let destination = toMatch ? this.canonicalLocation(toMatch[1]) : null;

    if (!origin || !destination) {
      // Fall back to order of appearance ("Djibouti to Indode" without
      // "from", or a bare location mention).
      const mentions: string[] = [];
      const all = text.matchAll(
        new RegExp(String.raw`\b(${LOCATION_ALTERNATION})\b`, 'gi'),
      );
      for (const m of all) {
        const canonical = this.canonicalLocation(m[1]);
        if (canonical && !mentions.includes(canonical)) mentions.push(canonical);
      }

      if (!origin && !destination) {
        origin = mentions[0] ?? null;
        destination = mentions[1] ?? null;
      } else if (!origin) {
        origin = mentions.find((loc) => loc !== destination) ?? null;
      } else {
        destination = mentions.find((loc) => loc !== origin) ?? null;
      }
    }

    return { origin, destination };
  }

  private canonicalLocation(raw: string): string | null {
    const normalized = raw.replace(/\s+/g, ' ').toLowerCase();
    return (
      KNOWN_LOCATIONS.find((loc) => loc.toLowerCase() === normalized) ?? null
    );
  }

  private resolveDirection(
    origin: string | null,
    destination: string | null,
  ): AiDirection | null {
    if (!origin || !destination) return null;
    if (origin === 'Djibouti' && INLAND_LOCATIONS.has(destination)) {
      return 'IMPORT';
    }
    if (INLAND_LOCATIONS.has(origin) && destination === 'Djibouti') {
      return 'EXPORT';
    }
    return null;
  }

  private extractCargoType(text: string): string | null {
    for (const [pattern, cargo] of CARGO_KEYWORDS) {
      if (pattern.test(text)) return cargo;
    }
    return null;
  }

  private extractContainerType(text: string): AiContainerType | null {
    // Lookbehind instead of \b: "2x40ft" has no word boundary before "40",
    // but "140ft" must not read as a 40ft container.
    if (/(?<!\d)40[\s-]?(?:ft|foot)\b/i.test(text)) return '40FT';
    if (/(?<!\d)20[\s-]?(?:ft|foot)\b/i.test(text)) return '20FT';
    if (/\bbulk\b/i.test(text)) return 'BULK';
    if (/\b(?:vehicles?|cars?)\b/i.test(text)) return 'RO_RO';
    return null;
  }

  private extractQuantity(text: string): number | null {
    // "2x40ft", "2 x 40ft", "3x20ft", "1x20ft"
    let match = text.match(/(\d+)\s*x\s*\d+\s*-?\s*(?:ft|foot)\b/i);
    if (match) return parseInt(match[1], 10);

    // "one 40ft container", "two containers"
    match = text.match(
      new RegExp(
        String.raw`\b(${Object.keys(WORD_NUMBERS).join('|')})\s+(?:\d+\s*-?\s*(?:ft|foot)\s+)?containers?\b`,
        'i',
      ),
    );
    if (match) return WORD_NUMBERS[match[1].toLowerCase()];

    // "3 containers", "2 refrigerated containers"
    match = text.match(/(\d+)\s+(?:[a-z]+\s+)?containers?\b/i);
    if (match) return parseInt(match[1], 10);

    // "5 vehicles", "3 cars"
    match = text.match(/(\d+)\s+(?:vehicles?|cars?)\b/i);
    if (match) return parseInt(match[1], 10);

    return null;
  }

  private extractWeightKg(text: string): number | null {
    const tons = text.match(/([\d,]+(?:\.\d+)?)\s*(?:tons?|tonnes?)\b/i);
    if (tons) return Math.round(this.parseNumber(tons[1]) * 1000);

    const kg = text.match(/([\d,]+(?:\.\d+)?)\s*kgs?\b/i);
    if (kg) return Math.round(this.parseNumber(kg[1]));

    return null;
  }

  private parseNumber(raw: string): number {
    return parseFloat(raw.replace(/,/g, ''));
  }

  private extractFlag(
    text: string,
    kind: 'pickup' | 'delivery',
  ): boolean | null {
    // "no pickup required" must read as false, so the negative wins.
    if (new RegExp(String.raw`\bno\s+${kind}\b`, 'i').test(text)) return false;
    if (new RegExp(String.raw`\b${kind}\s+required\b`, 'i').test(text)) {
      return true;
    }
    return null;
  }

  private validate(extracted: AiExtractedBooking): AiValidationResult {
    const errors: string[] = [];

    if (!extracted.customerName) errors.push('Customer name is missing');
    if (!extracted.origin) errors.push('Origin is missing');
    if (!extracted.destination) errors.push('Destination is missing');
    if (!extracted.cargoType) errors.push('Cargo type is missing');
    if (!extracted.containerType) errors.push('Container type is missing');
    if (extracted.quantity === null) errors.push('Quantity is missing');
    if (!extracted.direction) errors.push('Direction is missing');

    return { valid: errors.length === 0, errors };
  }
}
