import pool from '../config/database';
import { ProcessingJob, CreateProcessingJobInput, JobType, JobStatus } from './types';
import logger from '../utils/logger';

export class ProcessingJobModel {
  /**
   * Create new processing job
   */
  static async create(jobData: CreateProcessingJobInput): Promise<ProcessingJob> {
    const { document_id, type } = jobData;
    
    const query = `
      INSERT INTO processing_jobs (document_id, type, status, progress)
      VALUES ($1, $2, 'pending', 0)
      RETURNING *
    `;
    
    try {
      const result = await pool.query(query, [document_id, type]);
      logger.info(`Created processing job: ${type} for document: ${document_id}`);
      return result.rows[0];
    } catch (error) {
      logger.error('Error creating processing job:', error);
      throw error;
    }
  }

  /**
   * Find job by ID
   */
  static async findById(id: string): Promise<ProcessingJob | null> {
    const query = 'SELECT * FROM processing_jobs WHERE id = $1';
    
    try {
      const result = await pool.query(query, [id]);
      return result.rows[0] || null;
    } catch (error) {
      logger.error('Error finding job by ID:', error);
      throw error;
    }
  }

  /**
   * Get jobs by document ID
   */
  static async findByDocumentId(documentId: string): Promise<ProcessingJob[]> {
    const query = `
      SELECT * FROM processing_jobs 
      WHERE document_id = $1 
      ORDER BY created_at DESC
    `;
    
    try {
      const result = await pool.query(query, [documentId]);
      return result.rows;
    } catch (error) {
      logger.error('Error finding jobs by document ID:', error);
      throw error;
    }
  }

  /**
   * Get jobs by type
   */
  static async findByType(type: JobType, limit = 50, offset = 0): Promise<ProcessingJob[]> {
    const query = `
      SELECT * FROM processing_jobs 
      WHERE type = $1 
      ORDER BY created_at DESC 
      LIMIT $2 OFFSET $3
    `;
    
    try {
      const result = await pool.query(query, [type, limit, offset]);
      return result.rows;
    } catch (error) {
      logger.error('Error finding jobs by type:', error);
      throw error;
    }
  }

  /**
   * Get jobs by status
   */
  static async findByStatus(status: JobStatus, limit = 50, offset = 0): Promise<ProcessingJob[]> {
    const query = `
      SELECT * FROM processing_jobs 
      WHERE status = $1 
      ORDER BY created_at ASC 
      LIMIT $2 OFFSET $3
    `;
    
    try {
      const result = await pool.query(query, [status, limit, offset]);
      return result.rows;
    } catch (error) {
      logger.error('Error finding jobs by status:', error);
      throw error;
    }
  }

  /**
   * Get pending jobs (for job queue processing)
   */
  static async findPendingJobs(limit = 10): Promise<ProcessingJob[]> {
    const query = `
      SELECT * FROM processing_jobs 
      WHERE status = 'pending' 
      ORDER BY created_at ASC 
      LIMIT $1
    `;
    
    try {
      const result = await pool.query(query, [limit]);
      return result.rows;
    } catch (error) {
      logger.error('Error finding pending jobs:', error);
      throw error;
    }
  }

  /**
   * Get all jobs (for admin)
   */
  static async findAll(limit = 100, offset = 0): Promise<(ProcessingJob & { original_file_name: string, user_name: string })[]> {
    const query = `
      SELECT pj.*, d.original_file_name, u.name as user_name
      FROM processing_jobs pj
      JOIN documents d ON pj.document_id = d.id
      JOIN users u ON d.user_id = u.id
      ORDER BY pj.created_at DESC 
      LIMIT $1 OFFSET $2
    `;
    
    try {
      const result = await pool.query(query, [limit, offset]);
      return result.rows;
    } catch (error) {
      logger.error('Error finding all jobs:', error);
      throw error;
    }
  }

  /**
   * Update job status
   */
  static async updateStatus(id: string, status: JobStatus, additionalData?: any): Promise<ProcessingJob | null> {
    let query: string;
    let params: any[];
    
    if (additionalData) {
      // Build dynamic query for additional data
      const updateFields = ['status = $1'];
      params = [status];
      
      Object.entries(additionalData).forEach(([key, value], index) => {
        if (value !== undefined) {
          updateFields.push(`${key} = $${index + 3}`);
          params.push(value);
        }
      });
      
      // Add timestamp logic
      updateFields.push(`
        started_at = CASE WHEN $1 = 'processing' THEN COALESCE(started_at, CURRENT_TIMESTAMP) ELSE started_at END,
        completed_at = CASE WHEN $1 IN ('completed', 'failed') THEN CURRENT_TIMESTAMP ELSE completed_at END
      `);
      
      query = `
        UPDATE processing_jobs 
        SET ${updateFields.join(', ')}
        WHERE id = $2
        RETURNING *
      `;
      params.splice(1, 0, id);
    } else {
      query = `
        UPDATE processing_jobs 
        SET status = $1,
            started_at = CASE WHEN $1 = 'processing' THEN COALESCE(started_at, CURRENT_TIMESTAMP) ELSE started_at END,
            completed_at = CASE WHEN $1 IN ('completed', 'failed') THEN CURRENT_TIMESTAMP ELSE completed_at END
        WHERE id = $2
        RETURNING *
      `;
      params = [status, id];
    }
    
    try {
      const result = await pool.query(query, params);
      logger.info(`Updated job ${id} status to: ${status}${additionalData ? ' with additional data' : ''}`);
      return result.rows[0] || null;
    } catch (error) {
      logger.error('Error updating job status:', error);
      throw error;
    }
  }

  /**
   * Update job progress
   */
  static async updateProgress(id: string, progress: number): Promise<ProcessingJob | null> {
    const query = `
      UPDATE processing_jobs 
      SET progress = $1
      WHERE id = $2
      RETURNING *
    `;
    
    try {
      const result = await pool.query(query, [progress, id]);
      logger.info(`Updated job ${id} progress to: ${progress}%`);
      return result.rows[0] || null;
    } catch (error) {
      logger.error('Error updating job progress:', error);
      throw error;
    }
  }

  /**
   * Update job error message
   */
  static async updateErrorMessage(id: string, errorMessage: string): Promise<ProcessingJob | null> {
    const query = `
      UPDATE processing_jobs 
      SET error_message = $1
      WHERE id = $2
      RETURNING *
    `;
    
    try {
      const result = await pool.query(query, [errorMessage, id]);
      logger.info(`Updated error message for job: ${id}`);
      return result.rows[0] || null;
    } catch (error) {
      logger.error('Error updating job error message:', error);
      throw error;
    }
  }

  /**
   * Delete job
   */
  static async delete(id: string): Promise<boolean> {
    const query = 'DELETE FROM processing_jobs WHERE id = $1 RETURNING id';
    
    try {
      const result = await pool.query(query, [id]);
      const deleted = result.rows.length > 0;
      if (deleted) {
        logger.info(`Deleted job: ${id}`);
      }
      return deleted;
    } catch (error) {
      logger.error('Error deleting job:', error);
      throw error;
    }
  }

  /**
   * Delete jobs by document ID
   */
  static async deleteByDocumentId(documentId: string): Promise<number> {
    const query = 'DELETE FROM processing_jobs WHERE document_id = $1 RETURNING id';
    
    try {
      const result = await pool.query(query, [documentId]);
      const deletedCount = result.rows.length;
      if (deletedCount > 0) {
        logger.info(`Deleted ${deletedCount} jobs for document: ${documentId}`);
      }
      return deletedCount;
    } catch (error) {
      logger.error('Error deleting jobs by document ID:', error);
      throw error;
    }
  }

  /**
   * Count jobs by status
   */
  static async countByStatus(status: JobStatus): Promise<number> {
    const query = 'SELECT COUNT(*) FROM processing_jobs WHERE status = $1';
    
    try {
      const result = await pool.query(query, [status]);
      return parseInt(result.rows[0].count);
    } catch (error) {
      logger.error('Error counting jobs by status:', error);
      throw error;
    }
  }

  /**
   * Count total jobs
   */
  static async count(): Promise<number> {
    const query = 'SELECT COUNT(*) FROM processing_jobs';
    
    try {
      const result = await pool.query(query);
      return parseInt(result.rows[0].count);
    } catch (error) {
      logger.error('Error counting jobs:', error);
      throw error;
    }
  }

  /**
   * Get job statistics
   */
  static async getJobStatistics(): Promise<{
    total: number;
    pending: number;
    processing: number;
    completed: number;
    failed: number;
  }> {
    const query = `
      SELECT 
        COUNT(*) as total,
        COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending,
        COUNT(CASE WHEN status = 'processing' THEN 1 END) as processing,
        COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
        COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed
      FROM processing_jobs
    `;
    
    try {
      const result = await pool.query(query);
      return result.rows[0];
    } catch (error) {
      logger.error('Error getting job statistics:', error);
      throw error;
    }
  }

  /**
   * Find job by job ID (external job ID)
   */
  static async findByJobId(jobId: string): Promise<ProcessingJob | null> {
    const query = 'SELECT * FROM processing_jobs WHERE job_id = $1';
    
    try {
      const result = await pool.query(query, [jobId]);
      return result.rows[0] || null;
    } catch (error) {
      logger.error('Error finding job by job ID:', error);
      throw error;
    }
  }

  /**
   * Update job by job ID
   */
  static async updateByJobId(jobId: string, updateData: Partial<ProcessingJob>): Promise<ProcessingJob | null> {
    const fields = Object.keys(updateData);
    const values = Object.values(updateData);
    
    if (fields.length === 0) {
      return this.findByJobId(jobId);
    }
    
    const setClause = fields.map((field, index) => `${field} = $${index + 2}`).join(', ');
    const query = `
      UPDATE processing_jobs 
      SET ${setClause}
      WHERE job_id = $1
      RETURNING *
    `;
    
    try {
      const result = await pool.query(query, [jobId, ...values]);
      logger.info(`Updated job ${jobId} with fields: ${fields.join(', ')}`);
      return result.rows[0] || null;
    } catch (error) {
      logger.error('Error updating job by job ID:', error);
      throw error;
    }
  }
} 