// Database service layer with CRUD operations for all entities
import {
  doc,
  getDoc,
  getDocs,
  setDoc,
  updateDoc,
  deleteDoc,
  addDoc,
  query,
  where,
  orderBy,
  limit,
  startAfter,
  Timestamp,
  writeBatch,
  runTransaction,
  onSnapshot,
  QueryConstraint,
  DocumentSnapshot,
  QuerySnapshot,
} from 'firebase/firestore';
import { db } from '../firebase';
import {
  shopsCollection,
  usersCollection,
  customersCollection,
  customerMeasurementsCollection,
  inventoryCollection,
  tailoringItemsCollection,
  employeesCollection,
  ordersCollection,
  incomeTransactionsCollection,
  expenseTransactionsCollection,
  appearanceSettingsCollection,
  notificationSettingsCollection,
  auditLogsCollection,
  reportDataCollection,
  getShopDoc,
  getUserDoc,
  getCustomerDoc,
  getCustomerMeasurementDoc,
  getInventoryDoc,
  getTailoringItemDoc,
  getEmployeeDoc,
  getOrderDoc,
  getIncomeTransactionDoc,
  getExpenseTransactionDoc,
  getAppearanceSettingsDoc,
  getNotificationSettingsDoc,
  getAuditLogDoc,
  getReportDataDoc,
} from './schema';
import type {
  BaseDocument,
  ShopProfileDB,
  UserDB,
  CustomerDB,
  CustomerMeasurementDB,
  InventoryItemDB,
  TailoringItemDB,
  EmployeeDB,
  OrderDB,
  IncomeTransactionDB,
  ExpenseTransactionDB,
  AppearanceSettingsDB,
  NotificationSettingsDB,
  AuditLogDB,
  ReportDataDB,
  QueryResult,
  PaginationOptions,
  FilterOptions,
  DatabaseResult,
} from './types';

// Generic database service class
export class DatabaseService<T extends BaseDocument> {
  constructor(
    private collectionRef: any,
    private getDocRef: (id: string) => any
  ) {}

  // Create a new document
  async create(data: Omit<T, 'id' | 'createdAt' | 'updatedAt'>): Promise<DatabaseResult<T>> {
    try {
      const now = Timestamp.now();
      const docRef = doc(this.collectionRef);
      const newData = {
        ...data,
        id: docRef.id,
        createdAt: now,
        updatedAt: now,
      } as T;

      await setDoc(docRef, newData);
      
      // Log the creation
      await this.logAudit('create', docRef.id, newData);
      
      return { success: true, data: newData };
    } catch (error: any) {
      console.error('Error creating document:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Get a document by ID
  async getById(id: string): Promise<DatabaseResult<T>> {
    try {
      const docRef = this.getDocRef(id);
      const docSnap = await getDoc(docRef);
      
      if (docSnap.exists()) {
        const data = docSnap.data() as T;
        return { success: true, data };
      } else {
        return { success: false, error: 'Document not found' };
      }
    } catch (error: any) {
      console.error('Error getting document:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Update a document
  async update(id: string, data: Partial<Omit<T, 'id' | 'createdAt'>>): Promise<DatabaseResult<T>> {
    try {
      const docRef = this.getDocRef(id);
      const updateData = {
        ...data,
        updatedAt: Timestamp.now(),
      };

      await updateDoc(docRef, updateData);
      
      // Get the updated document
      const result = await this.getById(id);
      if (result.success && result.data) {
        // Log the update
        await this.logAudit('update', id, updateData);
        return result;
      }
      
      return { success: false, error: 'Failed to retrieve updated document' };
    } catch (error: any) {
      console.error('Error updating document:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Delete a document
  async delete(id: string): Promise<DatabaseResult<void>> {
    try {
      const docRef = this.getDocRef(id);
      
      // Get the document before deletion for audit log
      const docSnap = await getDoc(docRef);
      const oldData = docSnap.exists() ? docSnap.data() : null;
      
      await deleteDoc(docRef);
      
      // Log the deletion
      if (oldData) {
        await this.logAudit('delete', id, oldData);
      }
      
      return { success: true };
    } catch (error: any) {
      console.error('Error deleting document:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Get multiple documents with filters and pagination
  async getMany(
    filters: FilterOptions[] = [],
    pagination: PaginationOptions = {},
    shopId?: string
  ): Promise<DatabaseResult<QueryResult<T>>> {
    try {
      const constraints: QueryConstraint[] = [];
      
      // Add shop filter if provided
      if (shopId) {
        constraints.push(where('shopId', '==', shopId));
      }
      
      // Add custom filters
      filters.forEach(filter => {
        constraints.push(where(filter.field, filter.operator, filter.value));
      });
      
      // Add ordering
      if (pagination.orderBy) {
        constraints.push(orderBy(pagination.orderBy, pagination.orderDirection || 'desc'));
      }
      
      // Add pagination
      if (pagination.startAfter) {
        constraints.push(startAfter(pagination.startAfter));
      }
      
      if (pagination.limit) {
        constraints.push(limit(pagination.limit));
      }
      
      const q = query(this.collectionRef, ...constraints);
      const querySnapshot = await getDocs(q);
      
      const data: T[] = [];
      querySnapshot.forEach((doc) => {
        data.push(doc.data() as T);
      });
      
      const lastDoc = querySnapshot.docs[querySnapshot.docs.length - 1];
      const hasMore = querySnapshot.docs.length === (pagination.limit || 0);
      
      return {
        success: true,
        data: {
          data,
          lastDoc,
          hasMore,
          total: data.length,
        },
      };
    } catch (error: any) {
      console.error('Error getting documents:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Real-time listener for a document
  onDocumentChange(id: string, callback: (data: T | null) => void): () => void {
    const docRef = this.getDocRef(id);
    return onSnapshot(docRef, (doc) => {
      if (doc.exists()) {
        callback(doc.data() as T);
      } else {
        callback(null);
      }
    });
  }

  // Real-time listener for a collection
  onCollectionChange(
    callback: (data: T[]) => void,
    filters: FilterOptions[] = [],
    shopId?: string
  ): () => void {
    const constraints: QueryConstraint[] = [];
    
    if (shopId) {
      constraints.push(where('shopId', '==', shopId));
    }
    
    filters.forEach(filter => {
      constraints.push(where(filter.field, filter.operator, filter.value));
    });
    
    const q = query(this.collectionRef, ...constraints);
    
    return onSnapshot(q, (querySnapshot) => {
      const data: T[] = [];
      querySnapshot.forEach((doc) => {
        data.push(doc.data() as T);
      });
      callback(data);
    });
  }

  // Batch operations
  async batchCreate(items: Omit<T, 'id' | 'createdAt' | 'updatedAt'>[]): Promise<DatabaseResult<T[]>> {
    try {
      const batch = writeBatch(db);
      const now = Timestamp.now();
      const createdItems: T[] = [];
      
      items.forEach((item) => {
        const docRef = doc(this.collectionRef);
        const newData = {
          ...item,
          id: docRef.id,
          createdAt: now,
          updatedAt: now,
        } as T;
        
        batch.set(docRef, newData);
        createdItems.push(newData);
      });
      
      await batch.commit();
      
      return { success: true, data: createdItems };
    } catch (error: any) {
      console.error('Error batch creating documents:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Transaction operations
  async runTransaction<R>(
    updateFunction: (transaction: any) => Promise<R>
  ): Promise<DatabaseResult<R>> {
    try {
      const result = await runTransaction(db, updateFunction);
      return { success: true, data: result };
    } catch (error: any) {
      console.error('Error running transaction:', error);
      return { success: false, error: error.message, code: error.code };
    }
  }

  // Audit logging
  private async logAudit(action: 'create' | 'update' | 'delete', entityId: string, changes: any): Promise<void> {
    try {
      // This would be implemented based on current user context
      // For now, we'll skip audit logging to avoid circular dependencies
      console.log(`Audit: ${action} on ${entityId}`, changes);
    } catch (error) {
      console.error('Error logging audit:', error);
    }
  }
}

// Specific service instances
export const shopService = new DatabaseService<ShopProfileDB>(shopsCollection, getShopDoc);
export const userService = new DatabaseService<UserDB>(usersCollection, getUserDoc);
export const customerService = new DatabaseService<CustomerDB>(customersCollection, getCustomerDoc);
export const customerMeasurementService = new DatabaseService<CustomerMeasurementDB>(customerMeasurementsCollection, getCustomerMeasurementDoc);
export const inventoryService = new DatabaseService<InventoryItemDB>(inventoryCollection, getInventoryDoc);
export const tailoringItemService = new DatabaseService<TailoringItemDB>(tailoringItemsCollection, getTailoringItemDoc);
export const employeeService = new DatabaseService<EmployeeDB>(employeesCollection, getEmployeeDoc);
export const orderService = new DatabaseService<OrderDB>(ordersCollection, getOrderDoc);
export const incomeTransactionService = new DatabaseService<IncomeTransactionDB>(incomeTransactionsCollection, getIncomeTransactionDoc);
export const expenseTransactionService = new DatabaseService<ExpenseTransactionDB>(expenseTransactionsCollection, getExpenseTransactionDoc);
export const appearanceSettingsService = new DatabaseService<AppearanceSettingsDB>(appearanceSettingsCollection, getAppearanceSettingsDoc);
export const notificationSettingsService = new DatabaseService<NotificationSettingsDB>(notificationSettingsCollection, getNotificationSettingsDoc);
export const auditLogService = new DatabaseService<AuditLogDB>(auditLogsCollection, getAuditLogDoc);
export const reportDataService = new DatabaseService<ReportDataDB>(reportDataCollection, getReportDataDoc);

// Specialized service methods for complex operations

// Customer service extensions
export class CustomerServiceExtended extends DatabaseService<CustomerDB> {
  constructor() {
    super(customersCollection, getCustomerDoc);
  }

  // Get customers with their order statistics
  async getCustomersWithStats(shopId: string): Promise<DatabaseResult<CustomerDB[]>> {
    const result = await this.getMany([], { orderBy: 'totalSpent', orderDirection: 'desc' }, shopId);
    return result;
  }

  // Search customers by name or phone
  async searchCustomers(searchTerm: string, shopId: string): Promise<DatabaseResult<CustomerDB[]>> {
    // Note: Firestore doesn't support full-text search natively
    // This is a basic implementation - consider using Algolia or similar for better search
    const nameResult = await this.getMany([
      { field: 'name', operator: '>=', value: searchTerm },
      { field: 'name', operator: '<=', value: searchTerm + '\uf8ff' }
    ], {}, shopId);
    
    const phoneResult = await this.getMany([
      { field: 'phone', operator: '>=', value: searchTerm },
      { field: 'phone', operator: '<=', value: searchTerm + '\uf8ff' }
    ], {}, shopId);
    
    if (nameResult.success && phoneResult.success) {
      // Combine and deduplicate results
      const combined = [...(nameResult.data?.data || []), ...(phoneResult.data?.data || [])];
      const unique = combined.filter((customer, index, self) => 
        index === self.findIndex(c => c.id === customer.id)
      );
      
      return {
        success: true,
        data: {
          data: unique,
          hasMore: false,
          total: unique.length,
        },
      };
    }
    
    return { success: false, error: 'Search failed' };
  }
}

// Order service extensions
export class OrderServiceExtended extends DatabaseService<OrderDB> {
  constructor() {
    super(ordersCollection, getOrderDoc);
  }

  // Get orders by status
  async getOrdersByStatus(status: OrderDB['status'], shopId: string): Promise<DatabaseResult<QueryResult<OrderDB>>> {
    return this.getMany([{ field: 'status', operator: '==', value: status }], { orderBy: 'orderDate', orderDirection: 'desc' }, shopId);
  }

  // Get orders by date range
  async getOrdersByDateRange(startDate: Date, endDate: Date, shopId: string): Promise<DatabaseResult<QueryResult<OrderDB>>> {
    return this.getMany([
      { field: 'orderDate', operator: '>=', value: Timestamp.fromDate(startDate) },
      { field: 'orderDate', operator: '<=', value: Timestamp.fromDate(endDate) }
    ], { orderBy: 'orderDate', orderDirection: 'desc' }, shopId);
  }

  // Get orders assigned to an artisan
  async getOrdersByArtisan(artisanId: string, shopId: string): Promise<DatabaseResult<QueryResult<OrderDB>>> {
    return this.getMany([{ field: 'artisanId', operator: '==', value: artisanId }], { orderBy: 'orderDate', orderDirection: 'desc' }, shopId);
  }

  // Update order status with automatic timestamp updates
  async updateOrderStatus(orderId: string, status: OrderDB['status'], shopId: string): Promise<DatabaseResult<OrderDB>> {
    const updateData: Partial<OrderDB> = { status };
    
    // Add appropriate timestamp based on status
    const now = Timestamp.now();
    switch (status) {
      case 'In Progress':
        if (!updateData.artisanAssignedDate) {
          updateData.artisanAssignedDate = now;
        }
        break;
      case 'Ready for Delivery':
        updateData.artisanCompletedDate = now;
        break;
      case 'Delivered':
      case 'Completed':
        if (!updateData.artisanCompletedDate) {
          updateData.artisanCompletedDate = now;
        }
        break;
    }
    
    return this.update(orderId, updateData);
  }
}

// Inventory service extensions
export class InventoryServiceExtended extends DatabaseService<InventoryItemDB> {
  constructor() {
    super(inventoryCollection, getInventoryDoc);
  }

  // Get low stock items
  async getLowStockItems(shopId: string): Promise<DatabaseResult<QueryResult<InventoryItemDB>>> {
    // Note: This requires a composite index on shopId and quantity/lowStockThreshold
    const result = await this.getMany([], { orderBy: 'quantity', orderDirection: 'asc' }, shopId);
    
    if (result.success && result.data) {
      const lowStockItems = result.data.data.filter(item => item.quantity <= item.lowStockThreshold);
      return {
        success: true,
        data: {
          data: lowStockItems,
          hasMore: false,
          total: lowStockItems.length,
        },
      };
    }
    
    return result;
  }

  // Update inventory quantity (with transaction for consistency)
  async updateQuantity(itemId: string, quantityChange: number): Promise<DatabaseResult<InventoryItemDB>> {
    return this.runTransaction(async (transaction) => {
      const docRef = getInventoryDoc(itemId);
      const doc = await transaction.get(docRef);
      
      if (!doc.exists()) {
        throw new Error('Inventory item not found');
      }
      
      const currentData = doc.data() as InventoryItemDB;
      const newQuantity = currentData.quantity + quantityChange;
      
      if (newQuantity < 0) {
        throw new Error('Insufficient inventory');
      }
      
      const updateData = {
        quantity: newQuantity,
        updatedAt: Timestamp.now(),
      };
      
      transaction.update(docRef, updateData);
      
      return { ...currentData, ...updateData };
    });
  }
}

// Export extended services
export const customerServiceExtended = new CustomerServiceExtended();
export const orderServiceExtended = new OrderServiceExtended();
export const inventoryServiceExtended = new InventoryServiceExtended();

// Utility functions for common operations
export const dbUtils = {
  // Convert Firestore timestamp to Date
  timestampToDate: (timestamp: Timestamp): Date => timestamp.toDate(),
  
  // Convert Date to Firestore timestamp
  dateToTimestamp: (date: Date): Timestamp => Timestamp.fromDate(date),
  
  // Generate a new document ID
  generateId: () => doc(shopsCollection).id,
  
  // Batch delete multiple documents
  batchDelete: async (docRefs: any[]): Promise<DatabaseResult<void>> => {
    try {
      const batch = writeBatch(db);
      docRefs.forEach(docRef => batch.delete(docRef));
      await batch.commit();
      return { success: true };
    } catch (error: any) {
      return { success: false, error: error.message, code: error.code };
    }
  },
  
  // Get current timestamp
  now: () => Timestamp.now(),
};
