/**
 * @ai-summary NHTSA vPIC API client for VIN decoding
 * @ai-context Fetches vehicle data from NHTSA and caches results
 */

import axios, { AxiosError } from 'axios';
import { logger } from '../../../../core/logging/logger';
import { NHTSADecodeResponse, VinCacheEntry } from './nhtsa.types';
import { Pool } from 'pg';

/**
 * VIN validation regex
 * - 17 characters
 * - Excludes I, O, Q (not used in VINs)
 * - Alphanumeric only
 */
const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/;

/**
 * Cache TTL: 1 year (VIN data is static - vehicle specs don't change)
 */
const CACHE_TTL_SECONDS = 365 * 24 * 60 * 60;

export class NHTSAClient {
  private readonly baseURL = 'https://vpic.nhtsa.dot.gov/api';
  private readonly timeout = 5000; // 5 seconds

  constructor(private readonly pool: Pool) {}

  /**
   * Validate VIN format
   * @throws Error if VIN format is invalid
   */
  validateVin(vin: string): string {
    const sanitized = vin.trim().toUpperCase();

    if (!sanitized) {
      throw new Error('VIN is required');
    }

    if (!VIN_REGEX.test(sanitized)) {
      throw new Error('Invalid VIN format. VIN must be exactly 17 characters and contain only letters (except I, O, Q) and numbers.');
    }

    return sanitized;
  }

  /**
   * Check cache for existing VIN data
   */
  async getCached(vin: string): Promise<VinCacheEntry | null> {
    try {
      const result = await this.pool.query<{
        vin: string;
        make: string | null;
        model: string | null;
        year: number | null;
        engine_type: string | null;
        body_type: string | null;
        raw_data: NHTSADecodeResponse;
        cached_at: Date;
      }>(
        `SELECT vin, make, model, year, engine_type, body_type, raw_data, cached_at
         FROM vin_cache
         WHERE vin = $1
         AND cached_at > NOW() - INTERVAL '${CACHE_TTL_SECONDS} seconds'`,
        [vin]
      );

      if (result.rows.length === 0) {
        return null;
      }

      const row = result.rows[0];
      return {
        vin: row.vin,
        make: row.make,
        model: row.model,
        year: row.year,
        engineType: row.engine_type,
        bodyType: row.body_type,
        rawData: row.raw_data,
        cachedAt: row.cached_at,
      };
    } catch (error) {
      logger.error('Failed to check VIN cache', { vin, error });
      return null;
    }
  }

  /**
   * Save VIN data to cache
   */
  async saveToCache(vin: string, response: NHTSADecodeResponse): Promise<void> {
    try {
      const findValue = (variable: string): string | null => {
        const result = response.Results.find(r => r.Variable === variable);
        return result?.Value || null;
      };

      const year = findValue('Model Year');
      const make = findValue('Make');
      const model = findValue('Model');
      const engineType = findValue('Engine Model');
      const bodyType = findValue('Body Class');

      await this.pool.query(
        `INSERT INTO vin_cache (vin, make, model, year, engine_type, body_type, raw_data, cached_at)
         VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
         ON CONFLICT (vin) DO UPDATE SET
           make = EXCLUDED.make,
           model = EXCLUDED.model,
           year = EXCLUDED.year,
           engine_type = EXCLUDED.engine_type,
           body_type = EXCLUDED.body_type,
           raw_data = EXCLUDED.raw_data,
           cached_at = NOW()`,
        [vin, make, model, year ? parseInt(year) : null, engineType, bodyType, JSON.stringify(response)]
      );

      logger.debug('VIN cached', { vin });
    } catch (error) {
      logger.error('Failed to cache VIN data', { vin, error });
      // Don't throw - caching failure shouldn't break the decode flow
    }
  }

  /**
   * Decode VIN using NHTSA vPIC API
   * @param vin - 17-character VIN
   * @returns Raw NHTSA decode response
   * @throws Error if VIN is invalid or API call fails
   */
  async decodeVin(vin: string): Promise<NHTSADecodeResponse> {
    // Validate and sanitize VIN
    const sanitizedVin = this.validateVin(vin);

    // Check cache first
    const cached = await this.getCached(sanitizedVin);
    if (cached) {
      logger.debug('VIN cache hit', { vin: sanitizedVin });
      return cached.rawData;
    }

    // Call NHTSA API
    logger.info('Calling NHTSA vPIC API', { vin: sanitizedVin });

    try {
      const response = await axios.get<NHTSADecodeResponse>(
        `${this.baseURL}/vehicles/decodevin/${sanitizedVin}`,
        {
          params: { format: 'json' },
          timeout: this.timeout,
        }
      );

      // Check for NHTSA-level errors
      if (response.data.Count === 0) {
        throw new Error('NHTSA returned no results for this VIN');
      }

      // Check for error messages in results
      const errorResult = response.data.Results.find(
        r => r.Variable === 'Error Code' && r.Value && r.Value !== '0'
      );
      if (errorResult) {
        const errorText = response.data.Results.find(r => r.Variable === 'Error Text');
        throw new Error(`NHTSA error: ${errorText?.Value || 'Unknown error'}`);
      }

      // Cache the successful response
      await this.saveToCache(sanitizedVin, response.data);

      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const axiosError = error as AxiosError;
        if (axiosError.code === 'ECONNABORTED') {
          logger.error('NHTSA API timeout', { vin: sanitizedVin });
          throw new Error('NHTSA API request timed out. Please try again.');
        }
        if (axiosError.response) {
          logger.error('NHTSA API error response', {
            vin: sanitizedVin,
            status: axiosError.response.status,
            data: axiosError.response.data,
          });
          throw new Error(`NHTSA API error: ${axiosError.response.status}`);
        }
        logger.error('NHTSA API network error', { vin: sanitizedVin, message: axiosError.message });
        throw new Error('Unable to connect to NHTSA API. Please try again later.');
      }
      throw error;
    }
  }

  /**
   * Extract a specific value from NHTSA response
   */
  static extractValue(response: NHTSADecodeResponse, variable: string): string | null {
    const result = response.Results.find(r => r.Variable === variable);
    return result?.Value?.trim() || null;
  }

  /**
   * Extract year from NHTSA response
   */
  static extractYear(response: NHTSADecodeResponse): number | null {
    const value = NHTSAClient.extractValue(response, 'Model Year');
    if (!value) return null;
    const parsed = parseInt(value, 10);
    return isNaN(parsed) ? null : parsed;
  }

  /**
   * Extract engine description from NHTSA response
   * Combines multiple engine-related fields
   */
  static extractEngine(response: NHTSADecodeResponse): string | null {
    const engineModel = NHTSAClient.extractValue(response, 'Engine Model');
    if (engineModel) return engineModel;

    // Build engine description from components
    const cylinders = NHTSAClient.extractValue(response, 'Engine Number of Cylinders');
    const displacement = NHTSAClient.extractValue(response, 'Displacement (L)');
    const fuelType = NHTSAClient.extractValue(response, 'Fuel Type - Primary');

    const parts: string[] = [];
    if (cylinders) parts.push(`${cylinders}-Cylinder`);
    if (displacement) parts.push(`${displacement}L`);
    if (fuelType && fuelType !== 'Gasoline') parts.push(fuelType);

    return parts.length > 0 ? parts.join(' ') : null;
  }
}
