import { Request, Response, NextFunction } from 'express';
import { PgRestaurantOutletRepository } from '../../infrastructure/repositories/postgresql/restaurant.repository';
import { ApiError } from '../../utils/errors/api.error';
import logger from '../../config/logger';
import db from '../../infrastructure/database/connection';

// Initialize repositories
const outletRepository = new PgRestaurantOutletRepository();

/**
 * Sync Controller
 * Handles data synchronization from POS systems
 */
export class SyncController {
  /**
   * Sync sales data from POS
   * @route POST /api/sync/sales
   */
  async syncSales(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { posId, transactions } = req.body;

      if (!posId) {
        throw ApiError.badRequest('POS ID is required');
      }

      if (!transactions || !Array.isArray(transactions) || transactions.length === 0) {
        throw ApiError.badRequest('Transactions data is required and must be an array');
      }

      // Find outlet by POS ID
      const outlet = await outletRepository.findByPosId(posId);

      if (!outlet) {
        throw ApiError.notFound(`Outlet with POS ID ${posId} not found`);
      }

      // Create a sync log entry
      const [syncLog] = await db('pos_sync_logs').insert({
        outlet_id: outlet.id,
        pos_id: posId,
        sync_type: 'sales',
        sync_started_at: new Date(),
        status: 'processing',
        records_processed: 0,
        metadata: JSON.stringify({
          total_records: transactions.length,
          client_ip: req.ip
        })
      }).returning('id');

      // Start transaction
      const trx = await db.transaction();

      try {
        let processedCount = 0;
        let errorCount = 0;
        const errors: any[] = [];

        // Process each transaction
        for (const transaction of transactions) {
          try {
            // Validate transaction data
            if (!transaction.transaction_id || !transaction.transaction_date) {
              throw new Error(`Invalid transaction data: missing required fields`);
            }

            // Check if transaction already exists
            const existingTransaction = await trx('sales_data')
              .where({
                outlet_id: outlet.id,
                transaction_id: transaction.transaction_id
              })
              .first();

            if (existingTransaction) {
              // Update existing transaction
              await trx('sales_data')
                .where({
                  outlet_id: outlet.id,
                  transaction_id: transaction.transaction_id
                })
                .update({
                  total_amount: transaction.total_amount,
                  tax_amount: transaction.tax_amount,
                  discount_amount: transaction.discount_amount || 0,
                  payment_method: transaction.payment_method,
                  cashier_id: transaction.cashier_id,
                  items: JSON.stringify(transaction.items || []),
                  metadata: JSON.stringify(transaction.metadata || {}),
                  updated_at: new Date()
                });
            } else {
              // Insert new transaction
              await trx('sales_data').insert({
                outlet_id: outlet.id,
                pos_id: posId,
                transaction_id: transaction.transaction_id,
                transaction_date: new Date(transaction.transaction_date),
                total_amount: transaction.total_amount,
                tax_amount: transaction.tax_amount,
                discount_amount: transaction.discount_amount || 0,
                payment_method: transaction.payment_method,
                cashier_id: transaction.cashier_id,
                items: JSON.stringify(transaction.items || []),
                metadata: JSON.stringify(transaction.metadata || {})
              });
            }

            processedCount++;
          } catch (error) {
            errorCount++;
            errors.push({
              transaction_id: transaction.transaction_id,
              error: error instanceof Error ? error.message : 'Unknown error'
            });
            logger.error(`Error processing transaction ${transaction.transaction_id}:`, error);
          }
        }

        // Update sync log
        await trx('pos_sync_logs')
          .where({ id: syncLog.id })
          .update({
            sync_completed_at: new Date(),
            records_processed: processedCount,
            status: errorCount > 0 ? 'completed_with_errors' : 'completed',
            error_message: errorCount > 0 ? JSON.stringify(errors) : null,
            metadata: JSON.stringify({
              total_records: transactions.length,
              processed_records: processedCount,
              error_records: errorCount,
              client_ip: req.ip
            })
          });

        // Commit transaction
        await trx.commit();

        res.status(200).json({
          status: 'success',
          message: `Successfully processed sales data from POS ${posId}`,
          data: {
            sync_id: syncLog.id,
            total_records: transactions.length,
            processed_records: processedCount,
            error_records: errorCount
          }
        });
      } catch (error) {
        // Rollback transaction in case of error
        await trx.rollback();

        // Update sync log with error
        await db('pos_sync_logs')
          .where({ id: syncLog.id })
          .update({
            sync_completed_at: new Date(),
            status: 'failed',
            error_message: error instanceof Error ? error.message : 'Unknown error'
          });

        throw error;
      }
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get sync status
   * @route GET /api/sync/status/:syncId
   */
  async getSyncStatus(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const syncId = req.params.syncId;

      // Get sync log
      const syncLog = await db('pos_sync_logs')
        .where({ id: syncId })
        .first();

      if (!syncLog) {
        throw ApiError.notFound(`Sync log with ID ${syncId} not found`);
      }

      res.status(200).json({
        status: 'success',
        data: {
          sync_id: syncLog.id,
          outlet_id: syncLog.outlet_id,
          pos_id: syncLog.pos_id,
          sync_type: syncLog.sync_type,
          sync_started_at: syncLog.sync_started_at,
          sync_completed_at: syncLog.sync_completed_at,
          records_processed: syncLog.records_processed,
          status: syncLog.status,
          error_message: syncLog.error_message,
          metadata: syncLog.metadata
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get sync history for an outlet
   * @route GET /api/sync/history/:posId
   */
  async getSyncHistory(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const posId = req.params.posId;
      const page = parseInt(req.query.page as string) || 1;
      const limit = parseInt(req.query.limit as string) || 10;
      const startDate = req.query.startDate as string;
      const endDate = req.query.endDate as string;

      // Find outlet by POS ID
      const outlet = await outletRepository.findByPosId(posId);

      if (!outlet) {
        throw ApiError.notFound(`Outlet with POS ID ${posId} not found`);
      }

      // Build query
      let query = db('pos_sync_logs')
        .where({ pos_id: posId })
        .orderBy('sync_started_at', 'desc');

      // Apply date filters if provided
      if (startDate) {
        query = query.where('sync_started_at', '>=', new Date(startDate));
      }

      if (endDate) {
        query = query.where('sync_started_at', '<=', new Date(endDate));
      }

      // Get total count
      const totalCount = await query.clone().count('id as count').first();

      // Apply pagination
      const offset = (page - 1) * limit;
      const syncLogs = await query
        .offset(offset)
        .limit(limit);

      // Calculate pagination metadata
      const total = parseInt((totalCount?.count as string) || '0');
      const totalPages = Math.ceil(total / limit);
      const hasNext = page < totalPages;
      const hasPrev = page > 1;

      res.status(200).json({
        status: 'success',
        data: {
          sync_logs: syncLogs,
          pagination: {
            page,
            limit,
            total,
            totalPages,
            hasNext,
            hasPrev
          }
        }
      });
    } catch (error) {
      next(error);
    }
  }
}

// Export controller instance
export const syncController = new SyncController();
