import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Journal, JournalType } from './entities/index.js';
import { NotFoundError, ConflictError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

// ===== Interfaces =====

export interface CreateJournalDto {
  companyId: string;
  name: string;
  code: string;
  journalType: JournalType;
  defaultAccountId?: string;
  sequenceId?: string;
  currencyId?: string;
}

export interface UpdateJournalDto {
  name?: string;
  defaultAccountId?: string | null;
  sequenceId?: string | null;
  currencyId?: string | null;
  active?: boolean;
}

export interface JournalFilters {
  companyId?: string;
  journalType?: JournalType;
  active?: boolean;
  page?: number;
  limit?: number;
}

export interface JournalWithRelations extends Journal {
  companyName?: string;
  defaultAccountName?: string;
  currencyCode?: string;
}

// ===== JournalsService Class =====

class JournalsService {
  private journalRepository: Repository<Journal>;

  constructor() {
    this.journalRepository = AppDataSource.getRepository(Journal);
  }

  /**
   * Get all journals with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: JournalFilters = {}
  ): Promise<{ data: JournalWithRelations[]; total: number }> {
    try {
      const {
        companyId,
        journalType,
        active,
        page = 1,
        limit = 50
      } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.journalRepository
        .createQueryBuilder('journal')
        .leftJoin('journal.company', 'company')
        .addSelect(['company.name'])
        .leftJoin('journal.defaultAccount', 'defaultAccount')
        .addSelect(['defaultAccount.name'])
        .where('journal.tenantId = :tenantId', { tenantId })
        .andWhere('journal.deletedAt IS NULL');

      // Apply filters
      if (companyId) {
        queryBuilder.andWhere('journal.companyId = :companyId', { companyId });
      }

      if (journalType) {
        queryBuilder.andWhere('journal.journalType = :journalType', { journalType });
      }

      if (active !== undefined) {
        queryBuilder.andWhere('journal.active = :active', { active });
      }

      // Get total count
      const total = await queryBuilder.getCount();

      // Get paginated results
      const journals = await queryBuilder
        .orderBy('journal.code', 'ASC')
        .skip(skip)
        .take(limit)
        .getMany();

      // Map to include relation names
      const data: JournalWithRelations[] = journals.map(journal => ({
        ...journal,
        companyName: journal.company?.name,
        defaultAccountName: journal.defaultAccount?.name,
      }));

      logger.debug('Journals retrieved', { tenantId, count: data.length, total });

      return { data, total };
    } catch (error) {
      logger.error('Error retrieving journals', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get journal by ID
   */
  async findById(id: string, tenantId: string): Promise<JournalWithRelations> {
    try {
      const journal = await this.journalRepository
        .createQueryBuilder('journal')
        .leftJoin('journal.company', 'company')
        .addSelect(['company.name'])
        .leftJoin('journal.defaultAccount', 'defaultAccount')
        .addSelect(['defaultAccount.name'])
        .where('journal.id = :id', { id })
        .andWhere('journal.tenantId = :tenantId', { tenantId })
        .andWhere('journal.deletedAt IS NULL')
        .getOne();

      if (!journal) {
        throw new NotFoundError('Diario no encontrado');
      }

      return {
        ...journal,
        companyName: journal.company?.name,
        defaultAccountName: journal.defaultAccount?.name,
      };
    } catch (error) {
      logger.error('Error finding journal', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new journal
   */
  async create(
    dto: CreateJournalDto,
    tenantId: string,
    userId: string
  ): Promise<Journal> {
    try {
      // Validate unique code within company
      const existing = await this.journalRepository.findOne({
        where: {
          companyId: dto.companyId,
          code: dto.code,
          deletedAt: IsNull(),
        },
      });

      if (existing) {
        throw new ConflictError(`Ya existe un diario con codigo ${dto.code}`);
      }

      // Create journal
      const journal = this.journalRepository.create({
        tenantId,
        companyId: dto.companyId,
        name: dto.name,
        code: dto.code,
        journalType: dto.journalType,
        defaultAccountId: dto.defaultAccountId || null,
        sequenceId: dto.sequenceId || null,
        currencyId: dto.currencyId || null,
        createdBy: userId,
      });

      await this.journalRepository.save(journal);

      logger.info('Journal created', {
        journalId: journal.id,
        tenantId,
        code: journal.code,
        createdBy: userId,
      });

      return journal;
    } catch (error) {
      logger.error('Error creating journal', {
        error: (error as Error).message,
        tenantId,
        dto,
      });
      throw error;
    }
  }

  /**
   * Update a journal
   */
  async update(
    id: string,
    dto: UpdateJournalDto,
    tenantId: string,
    userId: string
  ): Promise<Journal> {
    try {
      const existing = await this.findById(id, tenantId);

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.defaultAccountId !== undefined) existing.defaultAccountId = dto.defaultAccountId;
      if (dto.sequenceId !== undefined) existing.sequenceId = dto.sequenceId;
      if (dto.currencyId !== undefined) existing.currencyId = dto.currencyId;
      if (dto.active !== undefined) existing.active = dto.active;

      existing.updatedBy = userId;
      existing.updatedAt = new Date();

      await this.journalRepository.save(existing);

      logger.info('Journal updated', {
        journalId: id,
        tenantId,
        updatedBy: userId,
      });

      return await this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error updating journal', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Soft delete a journal
   */
  async delete(id: string, tenantId: string, userId: string): Promise<void> {
    try {
      await this.findById(id, tenantId);

      // Check if journal has entries (use raw query for this check since JournalEntry may not be imported)
      const entriesCheck = await this.journalRepository.query(
        `SELECT COUNT(*) as count FROM financial.journal_entries WHERE journal_id = $1`,
        [id]
      );

      if (parseInt(entriesCheck[0]?.count || '0', 10) > 0) {
        throw new ConflictError('No se puede eliminar un diario que tiene polizas');
      }

      // Soft delete
      await this.journalRepository.update(
        { id, tenantId },
        {
          deletedAt: new Date(),
          deletedBy: userId,
        }
      );

      logger.info('Journal deleted', {
        journalId: id,
        tenantId,
        deletedBy: userId,
      });
    } catch (error) {
      logger.error('Error deleting journal', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }
}

// ===== Export Singleton Instance =====

export const journalsService = new JournalsService();

// Re-export JournalType for backwards compatibility
export { JournalType };
