import { Repository, DeepPartial } from 'typeorm';
import { AppDataSource } from '../../../config/typeorm';
import { BaseService } from '../../../shared/services/base.service';
import { ServiceResult } from '../../../shared/types';
import { POSSession, SessionStatus } from '../entities/pos-session.entity';
import { CashRegister, RegisterStatus } from '../../branches/entities/cash-register.entity';

export class POSSessionService extends BaseService<POSSession> {
  private registerRepository: Repository<CashRegister>;

  constructor() {
    super(AppDataSource.getRepository(POSSession));
    this.registerRepository = AppDataSource.getRepository(CashRegister);
  }

  /**
   * Generate session number
   */
  private generateSessionNumber(branchCode: string): string {
    const date = new Date();
    const dateStr = date.toISOString().slice(0, 10).replace(/-/g, '');
    const timeStr = date.toISOString().slice(11, 19).replace(/:/g, '');
    return `SES-${branchCode}-${dateStr}-${timeStr}`;
  }

  /**
   * Open a new POS session
   */
  async openSession(
    tenantId: string,
    data: {
      branchId: string;
      registerId: string;
      userId: string;
      openingCash: number;
      openingNotes?: string;
    }
  ): Promise<ServiceResult<POSSession>> {
    const queryRunner = AppDataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Check if register exists and is available
      const register = await this.registerRepository.findOne({
        where: { id: data.registerId, tenantId, branchId: data.branchId },
        relations: ['branch'],
      });

      if (!register) {
        return {
          success: false,
          error: {
            code: 'REGISTER_NOT_FOUND',
            message: 'Cash register not found',
          },
        };
      }

      if (register.status !== RegisterStatus.AVAILABLE) {
        return {
          success: false,
          error: {
            code: 'REGISTER_NOT_AVAILABLE',
            message: `Cash register is currently ${register.status}`,
          },
        };
      }

      // Check if user already has an open session
      const existingSession = await this.repository.findOne({
        where: {
          tenantId,
          userId: data.userId,
          status: SessionStatus.OPEN,
        },
      });

      if (existingSession) {
        return {
          success: false,
          error: {
            code: 'SESSION_ALREADY_OPEN',
            message: 'User already has an open session',
          },
        };
      }

      // Create session
      const branchCode = register.branch?.code || 'UNK';
      const session = this.repository.create({
        tenantId,
        branchId: data.branchId,
        registerId: data.registerId,
        userId: data.userId,
        number: this.generateSessionNumber(branchCode),
        status: SessionStatus.OPEN,
        openedAt: new Date(),
        openingCash: data.openingCash,
        openingNotes: data.openingNotes,
      });

      const savedSession = await queryRunner.manager.save(session);

      // Update register status
      await queryRunner.manager.update(CashRegister, register.id, {
        status: RegisterStatus.IN_USE,
        currentSessionId: savedSession.id,
        currentUserId: data.userId,
      });

      await queryRunner.commitTransaction();

      return { success: true, data: savedSession };
    } catch (error: any) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'OPEN_SESSION_FAILED',
          message: error.message || 'Failed to open session',
          details: error,
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Close a POS session
   */
  async closeSession(
    tenantId: string,
    sessionId: string,
    data: {
      closedBy: string;
      closingCashCounted: number;
      closingNotes?: string;
      cashCountDetail?: POSSession['cashCountDetail'];
    }
  ): Promise<ServiceResult<POSSession>> {
    const queryRunner = AppDataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const session = await this.repository.findOne({
        where: { id: sessionId, tenantId },
      });

      if (!session) {
        return {
          success: false,
          error: {
            code: 'SESSION_NOT_FOUND',
            message: 'Session not found',
          },
        };
      }

      if (session.status !== SessionStatus.OPEN) {
        return {
          success: false,
          error: {
            code: 'SESSION_NOT_OPEN',
            message: 'Session is not open',
          },
        };
      }

      // Calculate expected cash
      const expectedCash =
        session.openingCash +
        session.cashTotal +
        session.cashInTotal -
        session.cashOutTotal;

      const difference = data.closingCashCounted - expectedCash;

      // Update session
      session.status = SessionStatus.CLOSED;
      session.closedAt = new Date();
      session.closedBy = data.closedBy;
      session.closingCashCounted = data.closingCashCounted;
      session.closingCashExpected = expectedCash;
      session.closingDifference = difference;
      session.closingNotes = data.closingNotes;
      session.cashCountDetail = data.cashCountDetail;

      const savedSession = await queryRunner.manager.save(session);

      // Update register status
      await queryRunner.manager.update(CashRegister, session.registerId, {
        status: RegisterStatus.AVAILABLE,
        currentSessionId: null,
        currentUserId: null,
      });

      await queryRunner.commitTransaction();

      return { success: true, data: savedSession };
    } catch (error: any) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'CLOSE_SESSION_FAILED',
          message: error.message || 'Failed to close session',
          details: error,
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Get active session for a user
   */
  async getActiveSession(tenantId: string, userId: string): Promise<POSSession | null> {
    return this.repository.findOne({
      where: {
        tenantId,
        userId,
        status: SessionStatus.OPEN,
      },
    });
  }

  /**
   * Get active session for a register
   */
  async getRegisterSession(tenantId: string, registerId: string): Promise<POSSession | null> {
    return this.repository.findOne({
      where: {
        tenantId,
        registerId,
        status: SessionStatus.OPEN,
      },
    });
  }

  /**
   * Get session with orders
   */
  async getSessionWithOrders(tenantId: string, sessionId: string): Promise<POSSession | null> {
    return this.repository.findOne({
      where: { id: sessionId, tenantId },
      relations: ['orders'],
    });
  }

  /**
   * Update session totals after an order
   */
  async updateSessionTotals(
    sessionId: string,
    orderData: {
      total: number;
      discounts: number;
      cashAmount: number;
      cardAmount: number;
      transferAmount: number;
      otherAmount: number;
      itemsCount: number;
      isRefund: boolean;
    }
  ): Promise<void> {
    const session = await this.repository.findOne({ where: { id: sessionId } });
    if (!session) return;

    if (orderData.isRefund) {
      session.totalRefunds += orderData.total;
    } else {
      session.totalSales += orderData.total;
      session.ordersCount += 1;
    }

    session.totalDiscounts += orderData.discounts;
    session.cashTotal += orderData.cashAmount;
    session.cardTotal += orderData.cardAmount;
    session.transferTotal += orderData.transferAmount;
    session.otherTotal += orderData.otherAmount;
    session.itemsSold += orderData.itemsCount;

    await this.repository.save(session);
  }

  /**
   * Get sessions for a date range
   */
  async getSessionsByDateRange(
    tenantId: string,
    branchId: string,
    startDate: Date,
    endDate: Date
  ): Promise<POSSession[]> {
    return this.repository
      .createQueryBuilder('session')
      .where('session.tenantId = :tenantId', { tenantId })
      .andWhere('session.branchId = :branchId', { branchId })
      .andWhere('session.openedAt BETWEEN :startDate AND :endDate', {
        startDate,
        endDate,
      })
      .orderBy('session.openedAt', 'DESC')
      .getMany();
  }

  /**
   * Get daily session summary
   */
  async getDailySummary(
    tenantId: string,
    branchId: string,
    date: Date
  ): Promise<{
    sessionsCount: number;
    totalSales: number;
    totalRefunds: number;
    totalDiscounts: number;
    netSales: number;
    ordersCount: number;
  }> {
    const startOfDay = new Date(date);
    startOfDay.setHours(0, 0, 0, 0);

    const endOfDay = new Date(date);
    endOfDay.setHours(23, 59, 59, 999);

    const result = await this.repository
      .createQueryBuilder('session')
      .select('COUNT(*)', 'sessionsCount')
      .addSelect('COALESCE(SUM(session.totalSales), 0)', 'totalSales')
      .addSelect('COALESCE(SUM(session.totalRefunds), 0)', 'totalRefunds')
      .addSelect('COALESCE(SUM(session.totalDiscounts), 0)', 'totalDiscounts')
      .addSelect('COALESCE(SUM(session.ordersCount), 0)', 'ordersCount')
      .where('session.tenantId = :tenantId', { tenantId })
      .andWhere('session.branchId = :branchId', { branchId })
      .andWhere('session.openedAt BETWEEN :startDate AND :endDate', {
        startDate: startOfDay,
        endDate: endOfDay,
      })
      .getRawOne();

    return {
      sessionsCount: parseInt(result.sessionsCount) || 0,
      totalSales: parseFloat(result.totalSales) || 0,
      totalRefunds: parseFloat(result.totalRefunds) || 0,
      totalDiscounts: parseFloat(result.totalDiscounts) || 0,
      netSales:
        (parseFloat(result.totalSales) || 0) - (parseFloat(result.totalRefunds) || 0),
      ordersCount: parseInt(result.ordersCount) || 0,
    };
  }
}

// Export singleton instance
export const posSessionService = new POSSessionService();
