import { randomUUID } from 'crypto';
import type { CreateDocumentBody, DocumentRecord, DocumentType, UpdateDocumentBody } from './documents.types';
import { DocumentsRepository } from '../data/documents.repository';
import { OwnershipCostsService } from '../../ownership-costs/domain/ownership-costs.service';
import type { OwnershipCostType } from '../../ownership-costs/domain/ownership-costs.types';
import pool from '../../../core/config/database';

export class DocumentsService {
  private readonly repo = new DocumentsRepository(pool);
  private readonly ownershipCostsService = new OwnershipCostsService(pool);

  async createDocument(userId: string, body: CreateDocumentBody): Promise<DocumentRecord> {
    await this.assertVehicleOwnership(userId, body.vehicleId);

    // Validate shared vehicles if provided (insurance type only)
    if (body.sharedVehicleIds && body.sharedVehicleIds.length > 0) {
      if (body.documentType !== 'insurance') {
        const err: any = new Error('Shared vehicles are only supported for insurance documents');
        err.statusCode = 400;
        throw err;
      }
      // Validate ownership of all shared vehicles
      for (const vid of body.sharedVehicleIds) {
        await this.assertVehicleOwnership(userId, vid);
      }
    }

    const id = randomUUID();
    const doc = await this.repo.insert({
      id,
      userId,
      vehicleId: body.vehicleId,
      documentType: body.documentType as DocumentType,
      title: body.title,
      notes: body.notes ?? null,
      details: body.details ?? null,
      issuedDate: body.issuedDate ?? null,
      expirationDate: body.expirationDate ?? null,
      emailNotifications: body.emailNotifications ?? false,
      scanForMaintenance: body.scanForMaintenance ?? false,
      sharedVehicleIds: body.sharedVehicleIds ?? [],
    });

    // Auto-create ownership_cost when insurance/registration has cost data
    await this.autoCreateOwnershipCost(userId, doc, body);

    return doc;
  }

  /**
   * Auto-creates an ownership_cost record when an insurance or registration
   * document is created with cost data (premium or cost field in details).
   */
  private async autoCreateOwnershipCost(
    userId: string,
    doc: DocumentRecord,
    body: CreateDocumentBody
  ): Promise<void> {
    const costType = this.mapDocumentTypeToCostType(body.documentType);
    if (!costType) return; // Not a cost-linkable document type

    const costAmount = this.extractCostAmount(body);
    if (!costAmount || costAmount <= 0) return; // No valid cost data

    try {
      await this.ownershipCostsService.createCost(userId, {
        vehicleId: body.vehicleId,
        documentId: doc.id,
        costType,
        amount: costAmount,
        description: doc.title,
        periodStart: body.issuedDate,
        periodEnd: body.expirationDate,
      });
    } catch (err) {
      // Log but don't fail document creation if cost creation fails
      console.error('Failed to auto-create ownership cost for document:', doc.id, err);
    }
  }

  /**
   * Maps document types to ownership cost types.
   * Returns null for document types that don't auto-create costs.
   */
  private mapDocumentTypeToCostType(documentType: string): OwnershipCostType | null {
    const typeMap: Record<string, OwnershipCostType> = {
      'insurance': 'insurance',
      'registration': 'registration',
    };
    return typeMap[documentType] || null;
  }

  /**
   * Extracts cost amount from document details.
   * Insurance uses 'premium', registration uses 'cost'.
   */
  private extractCostAmount(body: CreateDocumentBody): number | null {
    if (!body.details) return null;

    const premium = body.details.premium;
    const cost = body.details.cost;

    if (typeof premium === 'number' && premium > 0) return premium;
    if (typeof cost === 'number' && cost > 0) return cost;

    return null;
  }

  async getDocument(userId: string, id: string): Promise<DocumentRecord | null> {
    return this.repo.findById(id, userId);
  }

  async listDocuments(userId: string, filters?: { vehicleId?: string; type?: DocumentType; expiresBefore?: string }) {
    return this.repo.listByUser(userId, filters);
  }

  async updateDocument(userId: string, id: string, patch: UpdateDocumentBody) {
    const existing = await this.repo.findById(id, userId);
    if (!existing) return null;

    // Validate shared vehicles if provided (insurance type only)
    if (patch.sharedVehicleIds !== undefined) {
      if (existing.documentType !== 'insurance') {
        const err: any = new Error('Shared vehicles are only supported for insurance documents');
        err.statusCode = 400;
        throw err;
      }
      // Validate ownership of all shared vehicles
      for (const vid of patch.sharedVehicleIds) {
        await this.assertVehicleOwnership(userId, vid);
      }
    }

    if (patch && typeof patch === 'object') {
      const updated = await this.repo.updateMetadata(id, userId, patch as any);

      // Sync cost changes to linked ownership_cost if applicable
      if (updated && patch.details) {
        await this.syncOwnershipCost(userId, updated, patch);
      }

      return updated;
    }
    return existing;
  }

  /**
   * Syncs cost data changes to linked ownership_cost record.
   * If document has linked cost and details.premium/cost changed, update it.
   */
  private async syncOwnershipCost(
    userId: string,
    doc: DocumentRecord,
    patch: UpdateDocumentBody
  ): Promise<void> {
    const costType = this.mapDocumentTypeToCostType(doc.documentType);
    if (!costType) return;

    const newCostAmount = this.extractCostAmountFromDetails(patch.details);
    if (newCostAmount === null) return; // No cost in update

    try {
      // Find existing linked cost
      const linkedCosts = await this.ownershipCostsService.getCosts(userId, { documentId: doc.id });

      if (linkedCosts.length > 0 && newCostAmount > 0) {
        // Update existing linked cost
        await this.ownershipCostsService.updateCost(userId, linkedCosts[0].id, {
          amount: newCostAmount,
          periodStart: patch.issuedDate ?? undefined,
          periodEnd: patch.expirationDate ?? undefined,
        });
      } else if (linkedCosts.length === 0 && newCostAmount > 0) {
        // Create new cost if none exists
        await this.ownershipCostsService.createCost(userId, {
          vehicleId: doc.vehicleId,
          documentId: doc.id,
          costType,
          amount: newCostAmount,
          description: doc.title,
          periodStart: patch.issuedDate ?? doc.issuedDate ?? undefined,
          periodEnd: patch.expirationDate ?? doc.expirationDate ?? undefined,
        });
      }
    } catch (err) {
      console.error('Failed to sync ownership cost for document:', doc.id, err);
    }
  }

  /**
   * Extracts cost amount from details object (for updates).
   */
  private extractCostAmountFromDetails(details?: Record<string, any> | null): number | null {
    if (!details) return null;

    const premium = details.premium;
    const cost = details.cost;

    if (typeof premium === 'number') return premium;
    if (typeof cost === 'number') return cost;

    return null;
  }

  async deleteDocument(userId: string, id: string): Promise<void> {
    // Note: Linked ownership_cost records are CASCADE deleted via FK
    await this.repo.softDelete(id, userId);
  }

  async addVehicleToDocument(userId: string, docId: string, vehicleId: string): Promise<DocumentRecord | null> {
    // Validate document exists and is owned by user
    const doc = await this.repo.findById(docId, userId);
    if (!doc) {
      const err: any = new Error('Document not found');
      err.statusCode = 404;
      throw err;
    }

    // Only insurance documents support shared vehicles
    if (doc.documentType !== 'insurance') {
      const err: any = new Error('Shared vehicles are only supported for insurance documents');
      err.statusCode = 400;
      throw err;
    }

    // Validate vehicle ownership
    await this.assertVehicleOwnership(userId, vehicleId);

    // Check if vehicle is already the primary vehicle
    if (doc.vehicleId === vehicleId) {
      const err: any = new Error('Vehicle is already the primary vehicle for this document');
      err.statusCode = 400;
      throw err;
    }

    // Add to shared vehicles (repository handles duplicate check)
    return this.repo.addSharedVehicle(docId, userId, vehicleId);
  }

  async removeVehicleFromDocument(userId: string, docId: string, vehicleId: string): Promise<DocumentRecord | null> {
    // Validate document exists and is owned by user
    const doc = await this.repo.findById(docId, userId);
    if (!doc) {
      const err: any = new Error('Document not found');
      err.statusCode = 404;
      throw err;
    }

    // Context-aware delete logic
    const isSharedVehicle = doc.sharedVehicleIds.includes(vehicleId);
    const isPrimaryVehicle = doc.vehicleId === vehicleId;

    if (!isSharedVehicle && !isPrimaryVehicle) {
      const err: any = new Error('Vehicle is not associated with this document');
      err.statusCode = 400;
      throw err;
    }

    // Case 1: Removing from shared vehicles only
    if (isSharedVehicle && !isPrimaryVehicle) {
      return this.repo.removeSharedVehicle(docId, userId, vehicleId);
    }

    // Case 2: Removing primary vehicle with no shared vehicles -> soft delete document
    if (isPrimaryVehicle && doc.sharedVehicleIds.length === 0) {
      await this.repo.softDelete(docId, userId);
      return null;
    }

    // Case 3: Removing primary vehicle with shared vehicles -> promote first shared to primary
    if (isPrimaryVehicle && doc.sharedVehicleIds.length > 0) {
      const newPrimaryId = doc.sharedVehicleIds[0];
      const remainingShared = doc.sharedVehicleIds.slice(1);

      // Update primary vehicle and remaining shared vehicles
      return this.repo.updateMetadata(docId, userId, {
        sharedVehicleIds: remainingShared,
      }).then(async () => {
        // Update vehicle_id separately as it's not part of the metadata update
        const res = await pool.query(
          'UPDATE documents SET vehicle_id = $1 WHERE id = $2 AND user_id = $3 AND deleted_at IS NULL RETURNING *',
          [newPrimaryId, docId, userId]
        );
        if (!res.rows[0]) return null;
        return this.repo.findById(docId, userId);
      });
    }

    return null;
  }

  async getDocumentsByVehicle(userId: string, vehicleId: string): Promise<DocumentRecord[]> {
    // Validate vehicle ownership
    await this.assertVehicleOwnership(userId, vehicleId);
    return this.repo.listByVehicle(userId, vehicleId);
  }

  private async assertVehicleOwnership(userId: string, vehicleId: string) {
    const res = await pool.query('SELECT id FROM vehicles WHERE id = $1 AND user_id = $2', [vehicleId, userId]);
    if (!res.rows[0]) {
      const err: any = new Error('Vehicle not found or not owned by user');
      err.statusCode = 403;
      throw err;
    }
  }
}

