/**
 * @ai-summary Fastify route handlers for vehicles API
 * @ai-context HTTP request/response handling with Fastify reply methods
 */

import { FastifyRequest, FastifyReply } from 'fastify';
import { VehiclesService, VehicleLimitExceededError } from '../domain/vehicles.service';
import { VehiclesRepository } from '../data/vehicles.repository';
import { pool } from '../../../core/config/database';
import { logger } from '../../../core/logging/logger';
import { CreateVehicleBody, UpdateVehicleBody, VehicleParams } from '../domain/vehicles.types';
import { getStorageService } from '../../../core/storage/storage.service';
import { NHTSAClient, DecodeVinRequest } from '../external/nhtsa';
import crypto from 'crypto';
import FileType from 'file-type';
import path from 'path';

export class VehiclesController {
  private vehiclesService: VehiclesService;
  private nhtsaClient: NHTSAClient;

  constructor() {
    const repository = new VehiclesRepository(pool);
    this.vehiclesService = new VehiclesService(repository, pool);
    this.nhtsaClient = new NHTSAClient(pool);
  }

  async getUserVehicles(request: FastifyRequest, reply: FastifyReply) {
    try {
      const userId = (request as any).user.sub;
      const vehicles = await this.vehiclesService.getUserVehicles(userId);
      
      return reply.code(200).send(vehicles);
    } catch (error) {
      logger.error('Error getting user vehicles', { error, userId: (request as any).user?.sub });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get vehicles'
      });
    }
  }

  async createVehicle(request: FastifyRequest<{ Body: CreateVehicleBody }>, reply: FastifyReply) {
    try {
      // Pre-1981 vehicles have no VIN/plate requirement
      const year = request.body?.year;
      const isPreModern = year && year < 1981;

      if (!isPreModern) {
        // Require either a valid 17-char VIN or a non-empty license plate
        const vin = request.body?.vin?.trim();
        const plate = request.body?.licensePlate?.trim();
        const hasValidVin = !!vin && vin.length === 17;
        const hasPlate = !!plate && plate.length > 0;
        if (!hasValidVin && !hasPlate) {
          return reply.code(400).send({
            error: 'Bad Request',
            message: 'Either a valid 17-character VIN or a license plate is required'
          });
        }
      }

      const userId = (request as any).user.sub;
      const vehicle = await this.vehiclesService.createVehicle(request.body, userId);

      return reply.code(201).send(vehicle);
    } catch (error: any) {
      logger.error('Error creating vehicle', { error, userId: (request as any).user?.sub });

      if (error instanceof VehicleLimitExceededError) {
        return reply.code(403).send({
          error: 'TIER_REQUIRED',
          requiredTier: error.tier === 'free' ? 'pro' : 'enterprise',
          currentTier: error.tier,
          feature: 'vehicle.addBeyondLimit',
          featureName: 'Additional Vehicles',
          upgradePrompt: error.upgradePrompt,
          context: {
            limit: error.limit,
            count: error.currentCount
          }
        });
      }

      if (error.message === 'Invalid VIN format') {
        return reply.code(400).send({
          error: 'Bad Request',
          message: error.message
        });
      }

      if (error.message === 'Vehicle with this VIN already exists') {
        return reply.code(400).send({
          error: 'Bad Request',
          message: error.message
        });
      }

      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to create vehicle'
      });
    }
  }

  async getVehicle(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
    try {
      const userId = (request as any).user.sub;
      const { id } = request.params;
      
      const vehicle = await this.vehiclesService.getVehicle(id, userId);
      
      return reply.code(200).send(vehicle);
    } catch (error: any) {
      logger.error('Error getting vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });
      
      if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
        return reply.code(404).send({
          error: 'Not Found',
          message: 'Vehicle not found'
        });
      }
      
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get vehicle'
      });
    }
  }

  async updateVehicle(request: FastifyRequest<{ Params: VehicleParams; Body: UpdateVehicleBody }>, reply: FastifyReply) {
    try {
      const userId = (request as any).user.sub;
      const { id } = request.params;

      const vehicle = await this.vehiclesService.updateVehicle(id, request.body, userId);

      return reply.code(200).send(vehicle);
    } catch (error: any) {
      logger.error('Error updating vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });

      if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
        return reply.code(404).send({
          error: 'Not Found',
          message: 'Vehicle not found'
        });
      }

      if (error.message === 'Invalid VIN format' ||
          error.message === 'Invalid VIN format for pre-1981 vehicle' ||
          error.message === 'Vehicle with this VIN already exists') {
        return reply.code(400).send({
          error: 'Bad Request',
          message: error.message
        });
      }

      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to update vehicle'
      });
    }
  }

  async deleteVehicle(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
    try {
      const userId = (request as any).user.sub;
      const { id } = request.params;

      await this.vehiclesService.deleteVehicle(id, userId);

      return reply.code(204).send();
    } catch (error: any) {
      logger.error('Error deleting vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });

      if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
        return reply.code(404).send({
          error: 'Not Found',
          message: 'Vehicle not found'
        });
      }

      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to delete vehicle'
      });
    }
  }

  async getTCO(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
    try {
      const userId = (request as any).user.sub;
      const { id } = request.params;

      const tco = await this.vehiclesService.getTCO(id, userId);
      return reply.code(200).send(tco);
    } catch (error: any) {
      logger.error('Error getting vehicle TCO', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });

      if (error.statusCode === 404 || error.message === 'Vehicle not found') {
        return reply.code(404).send({
          error: 'Not Found',
          message: 'Vehicle not found'
        });
      }

      if (error.statusCode === 403 || error.message === 'Unauthorized') {
        return reply.code(403).send({
          error: 'Forbidden',
          message: 'Not authorized to access this vehicle'
        });
      }

      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to calculate TCO'
      });
    }
  }

  async getDropdownMakes(request: FastifyRequest<{ Querystring: { year: number } }>, reply: FastifyReply) {
    try {
      const { year } = request.query;
      if (!year || isNaN(year)) {
        return reply.code(400).send({
          error: 'Bad Request',
          message: 'Valid year parameter is required'
        });
      }

      const makes = await this.vehiclesService.getDropdownMakes(year);
      return reply.code(200).send(makes);
    } catch (error) {
      logger.error('Error getting dropdown makes', { error, year: request.query?.year });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get makes'
      });
    }
  }

  async getDropdownModels(request: FastifyRequest<{ Querystring: { year: number; make: string } }>, reply: FastifyReply) {
    try {
      const { year, make } = request.query;
      if (!year || isNaN(year) || !make || make.trim().length === 0) {
        return reply.code(400).send({
          error: 'Bad Request',
          message: 'Valid year and make parameters are required'
        });
      }

      const models = await this.vehiclesService.getDropdownModels(year, make);
      return reply.code(200).send(models);
    } catch (error) {
      logger.error('Error getting dropdown models', { error, year: request.query?.year, make: request.query?.make });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get models'
      });
    }
  }

  async getDropdownTransmissions(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string; trim: string } }>, reply: FastifyReply) {
    try {
      const { year, make, model, trim } = request.query;
      if (!year || isNaN(year) || !make || !model || !trim || make.trim().length === 0 || model.trim().length === 0 || trim.trim().length === 0) {
        return reply.code(400).send({
          error: 'Bad Request',
          message: 'Valid year, make, model, and trim parameters are required'
        });
      }

      const transmissions = await this.vehiclesService.getDropdownTransmissions(year, make, model, trim);
      return reply.code(200).send(transmissions);
    } catch (error) {
      logger.error('Error getting dropdown transmissions', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model, trim: request.query?.trim });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get transmissions'
      });
    }
  }

  async getDropdownEngines(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string; trim: string } }>, reply: FastifyReply) {
    try {
      const { year, make, model, trim } = request.query;
      if (!year || isNaN(year) || !make || !model || !trim || make.trim().length === 0 || model.trim().length === 0 || trim.trim().length === 0) {
        return reply.code(400).send({
          error: 'Bad Request',
          message: 'Valid year, make, model, and trim parameters are required'
        });
      }

      const engines = await this.vehiclesService.getDropdownEngines(year, make, model, trim);
      return reply.code(200).send(engines);
    } catch (error) {
      logger.error('Error getting dropdown engines', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model, trim: request.query?.trim });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get engines'
      });
    }
  }

  async getDropdownTrims(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string } }>, reply: FastifyReply) {
    try {
      const { year, make, model } = request.query;
      if (!year || isNaN(year) || !make || !model || make.trim().length === 0 || model.trim().length === 0) {
        return reply.code(400).send({
          error: 'Bad Request',
          message: 'Valid year, make, and model parameters are required'
        });
      }

      const trims = await this.vehiclesService.getDropdownTrims(year, make, model);
      return reply.code(200).send(trims);
    } catch (error) {
      logger.error('Error getting dropdown trims', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get trims'
      });
    }
  }

  async getDropdownYears(_request: FastifyRequest, reply: FastifyReply) {
    try {
      // Use platform client through VehiclesService's integration
      const years = await this.vehiclesService.getDropdownYears();
      return reply.code(200).send(years);
    } catch (error) {
      logger.error('Error getting dropdown years', { error });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get years'
      });
    }
  }

  async getDropdownOptions(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string; trim: string; engine?: string; transmission?: string } }>, reply: FastifyReply) {
    try {
      const { year, make, model, trim, engine, transmission } = request.query;
      if (!year || isNaN(year) || !make || !model || !trim || make.trim().length === 0 || model.trim().length === 0 || trim.trim().length === 0) {
        return reply.code(400).send({
          error: 'Bad Request',
          message: 'Valid year, make, model, and trim parameters are required'
        });
      }

      const options = await this.vehiclesService.getDropdownOptions(year, make, model, trim, engine, transmission);
      return reply.code(200).send(options);
    } catch (error) {
      logger.error('Error getting dropdown options', { error, query: request.query });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to get engine/transmission options'
      });
    }
  }

  /**
   * Decode VIN using NHTSA vPIC API
   * POST /api/vehicles/decode-vin
   * Requires Pro or Enterprise tier
   */
  async decodeVin(request: FastifyRequest<{ Body: DecodeVinRequest }>, reply: FastifyReply) {
    const userId = (request as any).user?.sub;

    try {
      const { vin } = request.body;

      if (!vin || typeof vin !== 'string') {
        return reply.code(400).send({
          error: 'INVALID_VIN',
          message: 'VIN is required'
        });
      }

      logger.info('VIN decode requested', { userId, vin: vin.substring(0, 6) + '...' });

      // Validate and decode VIN
      const response = await this.nhtsaClient.decodeVin(vin);

      // Extract and map fields from NHTSA response
      const decodedData = await this.vehiclesService.mapNHTSAResponse(response);

      logger.info('VIN decode successful', {
        userId,
        hasYear: !!decodedData.year.value,
        hasMake: !!decodedData.make.value,
        hasModel: !!decodedData.model.value
      });

      return reply.code(200).send(decodedData);
    } catch (error: any) {
      logger.error('VIN decode failed', { error, userId });

      // Handle validation errors
      if (error.message?.includes('Invalid VIN')) {
        return reply.code(400).send({
          error: 'INVALID_VIN',
          message: error.message
        });
      }

      // Handle timeout
      if (error.message?.includes('timed out')) {
        return reply.code(504).send({
          error: 'VIN_DECODE_TIMEOUT',
          message: 'NHTSA API request timed out. Please try again.'
        });
      }

      // Handle NHTSA API errors
      if (error.message?.includes('NHTSA')) {
        return reply.code(502).send({
          error: 'VIN_DECODE_FAILED',
          message: 'Unable to decode VIN from external service',
          details: error.message
        });
      }

      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to decode VIN'
      });
    }
  }

  async uploadImage(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
    const userId = (request as any).user.sub;
    const vehicleId = request.params.id;

    logger.info('Vehicle image upload requested', {
      operation: 'vehicles.uploadImage',
      userId,
      vehicleId,
    });

    try {
      // Verify vehicle ownership
      const vehicle = await this.vehiclesService.getVehicleRaw(vehicleId, userId);
      if (!vehicle) {
        logger.warn('Vehicle not found for image upload', {
          operation: 'vehicles.uploadImage.not_found',
          userId,
          vehicleId,
        });
        return reply.code(404).send({ error: 'Not Found', message: 'Vehicle not found' });
      }

      const mp = await (request as any).file({ limits: { files: 1 } });
      if (!mp) {
        logger.warn('No file provided for image upload', {
          operation: 'vehicles.uploadImage.no_file',
          userId,
          vehicleId,
        });
        return reply.code(400).send({ error: 'Bad Request', message: 'No file provided' });
      }

      // Allowed image types
      const allowedTypes = new Map([
        ['image/jpeg', new Set(['image/jpeg'])],
        ['image/png', new Set(['image/png'])],
      ]);

      const contentType = mp.mimetype as string | undefined;
      if (!contentType || !allowedTypes.has(contentType)) {
        logger.warn('Unsupported image type', {
          operation: 'vehicles.uploadImage.unsupported_type',
          userId,
          vehicleId,
          contentType,
          fileName: mp.filename,
        });
        return reply.code(415).send({
          error: 'Unsupported Media Type',
          message: 'Only JPEG and PNG images are allowed'
        });
      }

      // Buffer the entire file for reliable processing
      // Vehicle images are typically small (< 10MB) so this is safe
      const chunks: Buffer[] = [];
      for await (const chunk of mp.file) {
        chunks.push(chunk);
      }
      const fileBuffer = Buffer.concat(chunks);

      // Validate actual file content using magic bytes
      const detectedType = await FileType.fromBuffer(fileBuffer);

      if (!detectedType) {
        logger.warn('Unable to detect file type from content', {
          operation: 'vehicles.uploadImage.type_detection_failed',
          userId,
          vehicleId,
          contentType,
          fileName: mp.filename,
        });
        return reply.code(415).send({
          error: 'Unsupported Media Type',
          message: 'Unable to verify file type from content'
        });
      }

      // Verify detected type matches claimed Content-Type
      const allowedDetectedTypes = allowedTypes.get(contentType);
      if (!allowedDetectedTypes || !allowedDetectedTypes.has(detectedType.mime)) {
        logger.warn('File content does not match Content-Type header', {
          operation: 'vehicles.uploadImage.type_mismatch',
          userId,
          vehicleId,
          claimedType: contentType,
          detectedType: detectedType.mime,
          fileName: mp.filename,
        });
        return reply.code(415).send({
          error: 'Unsupported Media Type',
          message: `File content (${detectedType.mime}) does not match claimed type (${contentType})`
        });
      }

      // Delete existing image if present
      if (vehicle.imageStorageKey && vehicle.imageStorageBucket) {
        const storage = getStorageService();
        try {
          await storage.deleteObject(vehicle.imageStorageBucket, vehicle.imageStorageKey);
          logger.info('Deleted existing vehicle image', {
            operation: 'vehicles.uploadImage.delete_existing',
            userId,
            vehicleId,
            storageKey: vehicle.imageStorageKey,
          });
        } catch (e) {
          logger.warn('Failed to delete existing vehicle image', {
            operation: 'vehicles.uploadImage.delete_existing_failed',
            userId,
            vehicleId,
            storageKey: vehicle.imageStorageKey,
            error: e instanceof Error ? e.message : 'Unknown error',
          });
        }
      }

      const originalName: string = mp.filename || 'vehicle-image';
      const ext = contentType === 'image/jpeg' ? 'jpg' : 'png';

      const storage = getStorageService();
      const bucket = 'vehicle-images';
      const unique = crypto.randomBytes(32).toString('hex');
      const key = `vehicle-images/${userId}/${vehicleId}/${unique}.${ext}`;

      // Write buffer directly to storage
      await storage.putObject(bucket, key, fileBuffer, contentType, { 'x-original-filename': originalName });

      const updated = await this.vehiclesService.updateVehicleImage(vehicleId, userId, {
        imageStorageBucket: bucket,
        imageStorageKey: key,
        imageFileName: originalName,
        imageContentType: contentType,
        imageFileSize: fileBuffer.length,
      });

      logger.info('Vehicle image upload completed', {
        operation: 'vehicles.uploadImage.success',
        userId,
        vehicleId,
        fileName: originalName,
        contentType,
        detectedType: detectedType.mime,
        fileSize: fileBuffer.length,
        storageKey: key,
      });

      return reply.code(200).send(updated);
    } catch (error) {
      logger.error('Error uploading vehicle image', { error, vehicleId, userId });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to upload image'
      });
    }
  }

  async downloadImage(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
    const userId = (request as any).user.sub;
    const vehicleId = request.params.id;

    logger.info('Vehicle image download requested', {
      operation: 'vehicles.downloadImage',
      userId,
      vehicleId,
    });

    try {
      const vehicle = await this.vehiclesService.getVehicleRaw(vehicleId, userId);
      if (!vehicle || !vehicle.imageStorageBucket || !vehicle.imageStorageKey) {
        logger.warn('Vehicle or image not found for download', {
          operation: 'vehicles.downloadImage.not_found',
          userId,
          vehicleId,
          hasVehicle: !!vehicle,
          hasStorageInfo: !!(vehicle?.imageStorageBucket && vehicle?.imageStorageKey),
        });
        return reply.code(404).send({ error: 'Not Found', message: 'Vehicle image not found' });
      }

      const storage = getStorageService();
      const contentType = vehicle.imageContentType || 'application/octet-stream';
      const filename = vehicle.imageFileName || path.basename(vehicle.imageStorageKey);

      reply.header('Content-Type', contentType);
      reply.header('Content-Disposition', `inline; filename="${encodeURIComponent(filename)}"`);

      logger.info('Vehicle image download initiated', {
        operation: 'vehicles.downloadImage.success',
        userId,
        vehicleId,
        fileName: filename,
        contentType,
        fileSize: vehicle.imageFileSize,
      });

      const stream = await storage.getObjectStream(vehicle.imageStorageBucket, vehicle.imageStorageKey);
      return reply.send(stream);
    } catch (error) {
      logger.error('Error downloading vehicle image', { error, vehicleId, userId });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to download image'
      });
    }
  }

  async deleteImage(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
    const userId = (request as any).user.sub;
    const vehicleId = request.params.id;

    logger.info('Vehicle image delete requested', {
      operation: 'vehicles.deleteImage',
      userId,
      vehicleId,
    });

    try {
      const vehicle = await this.vehiclesService.getVehicleRaw(vehicleId, userId);
      if (!vehicle) {
        logger.warn('Vehicle not found for image delete', {
          operation: 'vehicles.deleteImage.not_found',
          userId,
          vehicleId,
        });
        return reply.code(404).send({ error: 'Not Found', message: 'Vehicle not found' });
      }

      // Delete file from storage if exists
      if (vehicle.imageStorageKey && vehicle.imageStorageBucket) {
        const storage = getStorageService();
        try {
          await storage.deleteObject(vehicle.imageStorageBucket, vehicle.imageStorageKey);
          logger.info('Vehicle image file deleted from storage', {
            operation: 'vehicles.deleteImage.storage_cleanup',
            userId,
            vehicleId,
            storageKey: vehicle.imageStorageKey,
          });
        } catch (e) {
          logger.warn('Failed to delete vehicle image file from storage', {
            operation: 'vehicles.deleteImage.storage_cleanup_failed',
            userId,
            vehicleId,
            storageKey: vehicle.imageStorageKey,
            error: e instanceof Error ? e.message : 'Unknown error',
          });
        }
      }

      // Clear image metadata from database
      await this.vehiclesService.updateVehicleImage(vehicleId, userId, null);

      logger.info('Vehicle image deleted', {
        operation: 'vehicles.deleteImage.success',
        userId,
        vehicleId,
        hadFile: !!(vehicle.imageStorageKey),
      });

      return reply.code(204).send();
    } catch (error) {
      logger.error('Error deleting vehicle image', { error, vehicleId, userId });
      return reply.code(500).send({
        error: 'Internal server error',
        message: 'Failed to delete image'
      });
    }
  }
}
