import { Repository, DataSource } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Sequence, ResetPeriod } from './entities/sequence.entity.js';
import { NotFoundError, ValidationError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

// ============================================================================
// TYPES
// ============================================================================

export interface CreateSequenceDto {
  code: string;
  name: string;
  prefix?: string;
  suffix?: string;
  start_number?: number;
  startNumber?: number; // Accept camelCase too
  padding?: number;
  reset_period?: 'none' | 'year' | 'month';
  resetPeriod?: 'none' | 'year' | 'month'; // Accept camelCase too
}

export interface UpdateSequenceDto {
  name?: string;
  prefix?: string | null;
  suffix?: string | null;
  padding?: number;
  reset_period?: 'none' | 'year' | 'month';
  resetPeriod?: 'none' | 'year' | 'month'; // Accept camelCase too
  is_active?: boolean;
  isActive?: boolean; // Accept camelCase too
}

// ============================================================================
// PREDEFINED SEQUENCE CODES
// ============================================================================

export const SEQUENCE_CODES = {
  // Sales
  SALES_ORDER: 'SO',
  QUOTATION: 'QT',

  // Purchases
  PURCHASE_ORDER: 'PO',
  RFQ: 'RFQ',

  // Inventory
  PICKING_IN: 'WH/IN',
  PICKING_OUT: 'WH/OUT',
  PICKING_INT: 'WH/INT',
  INVENTORY_ADJ: 'INV/ADJ',

  // Financial
  INVOICE_CUSTOMER: 'INV',
  INVOICE_SUPPLIER: 'BILL',
  PAYMENT: 'PAY',
  JOURNAL_ENTRY: 'JE',

  // CRM
  LEAD: 'LEAD',
  OPPORTUNITY: 'OPP',

  // Projects
  PROJECT: 'PRJ',
  TASK: 'TASK',

  // HR
  EMPLOYEE: 'EMP',
  CONTRACT: 'CTR',
} as const;

// ============================================================================
// SERVICE
// ============================================================================

class SequencesService {
  private repository: Repository<Sequence>;
  private dataSource: DataSource;

  constructor() {
    this.repository = AppDataSource.getRepository(Sequence);
    this.dataSource = AppDataSource;
  }

  /**
   * Get the next number in a sequence using the database function
   * This is atomic and handles concurrent requests safely
   */
  async getNextNumber(
    sequenceCode: string,
    tenantId: string,
    queryRunner?: any
  ): Promise<string> {
    logger.debug('Generating next sequence number', { sequenceCode, tenantId });

    const executeQuery = queryRunner
      ? (sql: string, params: any[]) => queryRunner.query(sql, params)
      : (sql: string, params: any[]) => this.dataSource.query(sql, params);

    try {
      // Use the database function for atomic sequence generation
      const result = await executeQuery(
        `SELECT core.generate_next_sequence($1, $2) as sequence_number`,
        [sequenceCode, tenantId]
      );

      if (!result?.[0]?.sequence_number) {
        // Sequence doesn't exist, try to create it with default settings
        logger.warn('Sequence not found, creating default', {
          sequenceCode,
          tenantId,
        });

        await this.ensureSequenceExists(sequenceCode, tenantId, queryRunner);

        // Try again
        const retryResult = await executeQuery(
          `SELECT core.generate_next_sequence($1, $2) as sequence_number`,
          [sequenceCode, tenantId]
        );

        if (!retryResult?.[0]?.sequence_number) {
          throw new NotFoundError(`Secuencia ${sequenceCode} no encontrada`);
        }

        logger.debug('Generated sequence number after creating default', {
          sequenceCode,
          number: retryResult[0].sequence_number,
        });

        return retryResult[0].sequence_number;
      }

      logger.debug('Generated sequence number', {
        sequenceCode,
        number: result[0].sequence_number,
      });

      return result[0].sequence_number;
    } catch (error) {
      logger.error('Error generating sequence number', {
        sequenceCode,
        tenantId,
        error: (error as Error).message,
      });
      throw error;
    }
  }

  /**
   * Ensure a sequence exists, creating it with defaults if not
   */
  async ensureSequenceExists(
    sequenceCode: string,
    tenantId: string,
    queryRunner?: any
  ): Promise<void> {
    logger.debug('Ensuring sequence exists', { sequenceCode, tenantId });

    // Check if exists
    const existing = await this.repository.findOne({
      where: { code: sequenceCode, tenantId },
    });

    if (existing) {
      logger.debug('Sequence already exists', { sequenceCode, tenantId });
      return;
    }

    // Create with defaults based on code
    const defaults = this.getDefaultsForCode(sequenceCode);

    const sequence = this.repository.create({
      tenantId,
      code: sequenceCode,
      name: defaults.name,
      prefix: defaults.prefix,
      padding: defaults.padding,
      nextNumber: 1,
    });

    await this.repository.save(sequence);

    logger.info('Created default sequence', { sequenceCode, tenantId });
  }

  /**
   * Get default settings for a sequence code
   */
  private getDefaultsForCode(code: string): {
    name: string;
    prefix: string;
    padding: number;
  } {
    const defaults: Record<
      string,
      { name: string; prefix: string; padding: number }
    > = {
      [SEQUENCE_CODES.SALES_ORDER]: {
        name: 'Órdenes de Venta',
        prefix: 'SO-',
        padding: 5,
      },
      [SEQUENCE_CODES.QUOTATION]: {
        name: 'Cotizaciones',
        prefix: 'QT-',
        padding: 5,
      },
      [SEQUENCE_CODES.PURCHASE_ORDER]: {
        name: 'Órdenes de Compra',
        prefix: 'PO-',
        padding: 5,
      },
      [SEQUENCE_CODES.RFQ]: {
        name: 'Solicitudes de Cotización',
        prefix: 'RFQ-',
        padding: 5,
      },
      [SEQUENCE_CODES.PICKING_IN]: {
        name: 'Recepciones',
        prefix: 'WH/IN/',
        padding: 5,
      },
      [SEQUENCE_CODES.PICKING_OUT]: {
        name: 'Entregas',
        prefix: 'WH/OUT/',
        padding: 5,
      },
      [SEQUENCE_CODES.PICKING_INT]: {
        name: 'Transferencias',
        prefix: 'WH/INT/',
        padding: 5,
      },
      [SEQUENCE_CODES.INVENTORY_ADJ]: {
        name: 'Ajustes de Inventario',
        prefix: 'ADJ/',
        padding: 5,
      },
      [SEQUENCE_CODES.INVOICE_CUSTOMER]: {
        name: 'Facturas de Cliente',
        prefix: 'INV/',
        padding: 6,
      },
      [SEQUENCE_CODES.INVOICE_SUPPLIER]: {
        name: 'Facturas de Proveedor',
        prefix: 'BILL/',
        padding: 6,
      },
      [SEQUENCE_CODES.PAYMENT]: { name: 'Pagos', prefix: 'PAY/', padding: 5 },
      [SEQUENCE_CODES.JOURNAL_ENTRY]: {
        name: 'Asientos Contables',
        prefix: 'JE/',
        padding: 6,
      },
      [SEQUENCE_CODES.LEAD]: { name: 'Prospectos', prefix: 'LEAD-', padding: 5 },
      [SEQUENCE_CODES.OPPORTUNITY]: {
        name: 'Oportunidades',
        prefix: 'OPP-',
        padding: 5,
      },
      [SEQUENCE_CODES.PROJECT]: {
        name: 'Proyectos',
        prefix: 'PRJ-',
        padding: 4,
      },
      [SEQUENCE_CODES.TASK]: { name: 'Tareas', prefix: 'TASK-', padding: 5 },
      [SEQUENCE_CODES.EMPLOYEE]: {
        name: 'Empleados',
        prefix: 'EMP-',
        padding: 4,
      },
      [SEQUENCE_CODES.CONTRACT]: {
        name: 'Contratos',
        prefix: 'CTR-',
        padding: 5,
      },
    };

    return defaults[code] || { name: code, prefix: `${code}-`, padding: 5 };
  }

  /**
   * Get all sequences for a tenant
   */
  async findAll(tenantId: string): Promise<Sequence[]> {
    logger.debug('Finding all sequences', { tenantId });

    return this.repository.find({
      where: { tenantId },
      order: { code: 'ASC' },
    });
  }

  /**
   * Get a specific sequence by code
   */
  async findByCode(code: string, tenantId: string): Promise<Sequence | null> {
    logger.debug('Finding sequence by code', { code, tenantId });

    return this.repository.findOne({
      where: { code, tenantId },
    });
  }

  /**
   * Create a new sequence
   */
  async create(dto: CreateSequenceDto, tenantId: string): Promise<Sequence> {
    logger.debug('Creating sequence', { dto, tenantId });

    // Check for existing
    const existing = await this.findByCode(dto.code, tenantId);
    if (existing) {
      throw new ValidationError(
        `Ya existe una secuencia con código ${dto.code}`
      );
    }

    // Accept both snake_case and camelCase
    const startNumber = dto.start_number ?? dto.startNumber ?? 1;
    const resetPeriod = dto.reset_period ?? dto.resetPeriod ?? 'none';

    const sequence = this.repository.create({
      tenantId,
      code: dto.code,
      name: dto.name,
      prefix: dto.prefix || null,
      suffix: dto.suffix || null,
      nextNumber: startNumber,
      padding: dto.padding || 5,
      resetPeriod: resetPeriod as ResetPeriod,
    });

    const saved = await this.repository.save(sequence);

    logger.info('Sequence created', { code: dto.code, tenantId });

    return saved;
  }

  /**
   * Update a sequence
   */
  async update(
    code: string,
    dto: UpdateSequenceDto,
    tenantId: string
  ): Promise<Sequence> {
    logger.debug('Updating sequence', { code, dto, tenantId });

    const existing = await this.findByCode(code, tenantId);
    if (!existing) {
      throw new NotFoundError('Secuencia no encontrada');
    }

    // Accept both snake_case and camelCase
    const resetPeriod = dto.reset_period ?? dto.resetPeriod;
    const isActive = dto.is_active ?? dto.isActive;

    if (dto.name !== undefined) {
      existing.name = dto.name;
    }
    if (dto.prefix !== undefined) {
      existing.prefix = dto.prefix;
    }
    if (dto.suffix !== undefined) {
      existing.suffix = dto.suffix;
    }
    if (dto.padding !== undefined) {
      existing.padding = dto.padding;
    }
    if (resetPeriod !== undefined) {
      existing.resetPeriod = resetPeriod as ResetPeriod;
    }
    if (isActive !== undefined) {
      existing.isActive = isActive;
    }

    const updated = await this.repository.save(existing);

    logger.info('Sequence updated', { code, tenantId });

    return updated;
  }

  /**
   * Reset a sequence to a specific number
   */
  async reset(
    code: string,
    tenantId: string,
    newNumber: number = 1
  ): Promise<Sequence> {
    logger.debug('Resetting sequence', { code, tenantId, newNumber });

    const sequence = await this.findByCode(code, tenantId);
    if (!sequence) {
      throw new NotFoundError('Secuencia no encontrada');
    }

    sequence.nextNumber = newNumber;
    sequence.lastResetDate = new Date();

    const updated = await this.repository.save(sequence);

    logger.info('Sequence reset', { code, tenantId, newNumber });

    return updated;
  }

  /**
   * Preview what the next number would be (without incrementing)
   */
  async preview(code: string, tenantId: string): Promise<string> {
    logger.debug('Previewing next sequence number', { code, tenantId });

    const sequence = await this.findByCode(code, tenantId);
    if (!sequence) {
      throw new NotFoundError('Secuencia no encontrada');
    }

    const paddedNumber = String(sequence.nextNumber).padStart(
      sequence.padding,
      '0'
    );
    const prefix = sequence.prefix || '';
    const suffix = sequence.suffix || '';

    return `${prefix}${paddedNumber}${suffix}`;
  }

  /**
   * Initialize all standard sequences for a new tenant
   */
  async initializeForTenant(tenantId: string): Promise<void> {
    logger.debug('Initializing sequences for tenant', { tenantId });

    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      for (const [key, code] of Object.entries(SEQUENCE_CODES)) {
        await this.ensureSequenceExists(code, tenantId, queryRunner);
      }

      await queryRunner.commitTransaction();

      logger.info('Initialized sequences for tenant', {
        tenantId,
        count: Object.keys(SEQUENCE_CODES).length,
      });
    } catch (error) {
      await queryRunner.rollbackTransaction();
      logger.error('Error initializing sequences for tenant', {
        tenantId,
        error: (error as Error).message,
      });
      throw error;
    } finally {
      await queryRunner.release();
    }
  }
}

export const sequencesService = new SequencesService();
