import { Pool } from 'pg';
import pool from '../../../core/config/database';
import type { DocumentRecord, DocumentType } from '../domain/documents.types';

export class DocumentsRepository {
  constructor(private readonly db: Pool = pool) {}

  // ========================
  // Row Mapper
  // ========================

  private mapDocumentRecord(row: any): DocumentRecord {
    return {
      id: row.id,
      userId: row.user_id,
      vehicleId: row.vehicle_id,
      documentType: row.document_type,
      title: row.title,
      notes: row.notes,
      details: row.details,
      storageBucket: row.storage_bucket,
      storageKey: row.storage_key,
      fileName: row.file_name,
      contentType: row.content_type,
      fileSize: row.file_size,
      fileHash: row.file_hash,
      issuedDate: row.issued_date,
      expirationDate: row.expiration_date,
      emailNotifications: row.email_notifications,
      scanForMaintenance: row.scan_for_maintenance,
      sharedVehicleIds: row.shared_vehicle_ids || [],
      createdAt: row.created_at,
      updatedAt: row.updated_at,
      deletedAt: row.deleted_at
    };
  }

  // ========================
  // CRUD Operations
  // ========================

  async insert(doc: {
    id: string;
    userId: string;
    vehicleId: string;
    documentType: DocumentType;
    title: string;
    notes?: string | null;
    details?: any;
    issuedDate?: string | null;
    expirationDate?: string | null;
    emailNotifications?: boolean;
    scanForMaintenance?: boolean;
    sharedVehicleIds?: string[];
  }): Promise<DocumentRecord> {
    const res = await this.db.query(
      `INSERT INTO documents (
        id, user_id, vehicle_id, document_type, title, notes, details, issued_date, expiration_date, email_notifications, scan_for_maintenance, shared_vehicle_ids
      ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
      RETURNING *`,
      [
        doc.id,
        doc.userId,
        doc.vehicleId,
        doc.documentType,
        doc.title,
        doc.notes ?? null,
        doc.details ?? null,
        doc.issuedDate ?? null,
        doc.expirationDate ?? null,
        doc.emailNotifications ?? false,
        doc.scanForMaintenance ?? false,
        doc.sharedVehicleIds ?? [],
      ]
    );
    return this.mapDocumentRecord(res.rows[0]);
  }

  async findById(id: string, userId: string): Promise<DocumentRecord | null> {
    const res = await this.db.query(`SELECT * FROM documents WHERE id = $1 AND user_id = $2 AND deleted_at IS NULL`, [id, userId]);
    return res.rows[0] ? this.mapDocumentRecord(res.rows[0]) : null;
  }

  async listByUser(userId: string, filters?: { vehicleId?: string; type?: DocumentType; expiresBefore?: string }): Promise<DocumentRecord[]> {
    const conds: string[] = ['user_id = $1', 'deleted_at IS NULL'];
    const params: any[] = [userId];
    let i = 2;
    if (filters?.vehicleId) { conds.push(`vehicle_id = $${i++}`); params.push(filters.vehicleId); }
    if (filters?.type) { conds.push(`document_type = $${i++}`); params.push(filters.type); }
    if (filters?.expiresBefore) { conds.push(`expiration_date <= $${i++}`); params.push(filters.expiresBefore); }
    const sql = `SELECT * FROM documents WHERE ${conds.join(' AND ')} ORDER BY created_at DESC`;
    const res = await this.db.query(sql, params);
    return res.rows.map(row => this.mapDocumentRecord(row));
  }

  async batchInsert(
    documents: Array<{
      id: string;
      userId: string;
      vehicleId: string;
      documentType: DocumentType;
      title: string;
      notes?: string | null;
      details?: any;
      issuedDate?: string | null;
      expirationDate?: string | null;
      emailNotifications?: boolean;
      scanForMaintenance?: boolean;
      sharedVehicleIds?: string[];
    }>,
    client?: any
  ): Promise<DocumentRecord[]> {
    if (documents.length === 0) {
      return [];
    }

    // Multi-value INSERT for performance (avoids N round-trips)
    const queryClient = client || this.db;
    const placeholders: string[] = [];
    const values: any[] = [];
    let paramCount = 1;

    documents.forEach((doc) => {
      const docParams = [
        doc.id,
        doc.userId,
        doc.vehicleId,
        doc.documentType,
        doc.title,
        doc.notes ?? null,
        doc.details ?? null,
        doc.issuedDate ?? null,
        doc.expirationDate ?? null,
        doc.emailNotifications ?? false,
        doc.scanForMaintenance ?? false,
        doc.sharedVehicleIds ?? []
      ];

      const placeholder = `($${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++})`;
      placeholders.push(placeholder);
      values.push(...docParams);
    });

    const query = `
      INSERT INTO documents (
        id, user_id, vehicle_id, document_type, title, notes, details, issued_date, expiration_date, email_notifications, scan_for_maintenance, shared_vehicle_ids
      )
      VALUES ${placeholders.join(', ')}
      RETURNING *
    `;

    const result = await queryClient.query(query, values);
    return result.rows.map((row: any) => this.mapDocumentRecord(row));
  }

  async softDelete(id: string, userId: string): Promise<void> {
    await this.db.query(`UPDATE documents SET deleted_at = NOW() WHERE id = $1 AND user_id = $2`, [id, userId]);
  }

  async updateMetadata(id: string, userId: string, patch: Partial<Pick<DocumentRecord, 'title'|'notes'|'details'|'issuedDate'|'expirationDate'|'emailNotifications'|'scanForMaintenance'|'sharedVehicleIds'>>): Promise<DocumentRecord | null> {
    const fields: string[] = [];
    const params: any[] = [];
    let i = 1;
    if (patch.title !== undefined) { fields.push(`title = $${i++}`); params.push(patch.title); }
    if (patch.notes !== undefined) { fields.push(`notes = $${i++}`); params.push(patch.notes); }
    if (patch.details !== undefined) { fields.push(`details = $${i++}`); params.push(patch.details); }
    if (patch.issuedDate !== undefined) { fields.push(`issued_date = $${i++}`); params.push(patch.issuedDate); }
    if (patch.expirationDate !== undefined) { fields.push(`expiration_date = $${i++}`); params.push(patch.expirationDate); }
    if (patch.emailNotifications !== undefined) { fields.push(`email_notifications = $${i++}`); params.push(patch.emailNotifications); }
    if (patch.scanForMaintenance !== undefined) { fields.push(`scan_for_maintenance = $${i++}`); params.push(patch.scanForMaintenance); }
    if (patch.sharedVehicleIds !== undefined) { fields.push(`shared_vehicle_ids = $${i++}`); params.push(patch.sharedVehicleIds); }
    if (!fields.length) return this.findById(id, userId);
    params.push(id, userId);
    const sql = `UPDATE documents SET ${fields.join(', ')} WHERE id = $${i++} AND user_id = $${i++} AND deleted_at IS NULL RETURNING *`;
    const res = await this.db.query(sql, params);
    return res.rows[0] ? this.mapDocumentRecord(res.rows[0]) : null;
  }

  async updateStorageMeta(id: string, userId: string, meta: {
    storageBucket: string; storageKey: string; fileName: string; contentType: string; fileSize: number; fileHash?: string | null;
  }): Promise<DocumentRecord | null> {
    const res = await this.db.query(
      `UPDATE documents SET
        storage_bucket = $1,
        storage_key = $2,
        file_name = $3,
        content_type = $4,
        file_size = $5,
        file_hash = $6
       WHERE id = $7 AND user_id = $8 AND deleted_at IS NULL
       RETURNING *`,
      [meta.storageBucket, meta.storageKey, meta.fileName, meta.contentType, meta.fileSize, meta.fileHash ?? null, id, userId]
    );
    return res.rows[0] ? this.mapDocumentRecord(res.rows[0]) : null;
  }

  // ========================
  // Shared Vehicle Operations (Atomic)
  // ========================

  /**
   * Atomically add a vehicle to the shared_vehicle_ids array.
   * Uses PostgreSQL array_append() to avoid race conditions.
   */
  async addSharedVehicle(docId: string, userId: string, vehicleId: string): Promise<DocumentRecord | null> {
    const res = await this.db.query(
      `UPDATE documents
       SET shared_vehicle_ids = array_append(shared_vehicle_ids, $1::uuid)
       WHERE id = $2 AND user_id = $3 AND deleted_at IS NULL
         AND NOT ($1::uuid = ANY(shared_vehicle_ids))
       RETURNING *`,
      [vehicleId, docId, userId]
    );
    return res.rows[0] ? this.mapDocumentRecord(res.rows[0]) : null;
  }

  /**
   * Atomically remove a vehicle from the shared_vehicle_ids array.
   * Uses PostgreSQL array_remove() to avoid race conditions.
   */
  async removeSharedVehicle(docId: string, userId: string, vehicleId: string): Promise<DocumentRecord | null> {
    const res = await this.db.query(
      `UPDATE documents
       SET shared_vehicle_ids = array_remove(shared_vehicle_ids, $1::uuid)
       WHERE id = $2 AND user_id = $3 AND deleted_at IS NULL
       RETURNING *`,
      [vehicleId, docId, userId]
    );
    return res.rows[0] ? this.mapDocumentRecord(res.rows[0]) : null;
  }

  /**
   * List all documents associated with a vehicle (either as primary or shared).
   * Returns documents where vehicle_id = vehicleId OR vehicleId = ANY(shared_vehicle_ids).
   */
  async listByVehicle(userId: string, vehicleId: string): Promise<DocumentRecord[]> {
    const res = await this.db.query(
      `SELECT * FROM documents
       WHERE user_id = $1
         AND deleted_at IS NULL
         AND (vehicle_id = $2 OR $2::uuid = ANY(shared_vehicle_ids))
       ORDER BY created_at DESC`,
      [userId, vehicleId]
    );
    return res.rows.map(row => this.mapDocumentRecord(row));
  }
}

