import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Account, AccountType } from './entities/index.js';
import { NotFoundError, ValidationError, ForbiddenError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';

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

export interface CreateAccountDto {
  companyId: string;
  code: string;
  name: string;
  accountTypeId: string;
  parentId?: string;
  currencyId?: string;
  isReconcilable?: boolean;
  notes?: string;
}

export interface UpdateAccountDto {
  name?: string;
  parentId?: string | null;
  currencyId?: string | null;
  isReconcilable?: boolean;
  isDeprecated?: boolean;
  notes?: string | null;
}

export interface AccountFilters {
  companyId?: string;
  accountTypeId?: string;
  parentId?: string;
  isDeprecated?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

export interface AccountWithRelations extends Account {
  accountTypeName?: string;
  accountTypeCode?: string;
  parentName?: string;
  currencyCode?: string;
}

// ===== AccountsService Class =====

class AccountsService {
  private accountRepository: Repository<Account>;
  private accountTypeRepository: Repository<AccountType>;

  constructor() {
    this.accountRepository = AppDataSource.getRepository(Account);
    this.accountTypeRepository = AppDataSource.getRepository(AccountType);
  }

  /**
   * Get all account types (catalog)
   */
  async findAllAccountTypes(): Promise<AccountType[]> {
    return this.accountTypeRepository.find({
      order: { code: 'ASC' },
    });
  }

  /**
   * Get account type by ID
   */
  async findAccountTypeById(id: string): Promise<AccountType> {
    const accountType = await this.accountTypeRepository.findOne({
      where: { id },
    });

    if (!accountType) {
      throw new NotFoundError('Tipo de cuenta no encontrado');
    }

    return accountType;
  }

  /**
   * Get all accounts with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: AccountFilters = {}
  ): Promise<{ data: AccountWithRelations[]; total: number }> {
    try {
      const {
        companyId,
        accountTypeId,
        parentId,
        isDeprecated,
        search,
        page = 1,
        limit = 50
      } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.accountRepository
        .createQueryBuilder('account')
        .leftJoin('account.accountType', 'accountType')
        .addSelect(['accountType.name', 'accountType.code'])
        .leftJoin('account.parent', 'parent')
        .addSelect(['parent.name'])
        .where('account.tenantId = :tenantId', { tenantId })
        .andWhere('account.deletedAt IS NULL');

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

      if (accountTypeId) {
        queryBuilder.andWhere('account.accountTypeId = :accountTypeId', { accountTypeId });
      }

      if (parentId !== undefined) {
        if (parentId === null || parentId === 'null') {
          queryBuilder.andWhere('account.parentId IS NULL');
        } else {
          queryBuilder.andWhere('account.parentId = :parentId', { parentId });
        }
      }

      if (isDeprecated !== undefined) {
        queryBuilder.andWhere('account.isDeprecated = :isDeprecated', { isDeprecated });
      }

      if (search) {
        queryBuilder.andWhere(
          '(account.code ILIKE :search OR account.name ILIKE :search)',
          { search: `%${search}%` }
        );
      }

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

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

      // Map to include relation names
      const data: AccountWithRelations[] = accounts.map(account => ({
        ...account,
        accountTypeName: account.accountType?.name,
        accountTypeCode: account.accountType?.code,
        parentName: account.parent?.name,
      }));

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

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

  /**
   * Get account by ID
   */
  async findById(id: string, tenantId: string): Promise<AccountWithRelations> {
    try {
      const account = await this.accountRepository
        .createQueryBuilder('account')
        .leftJoin('account.accountType', 'accountType')
        .addSelect(['accountType.name', 'accountType.code'])
        .leftJoin('account.parent', 'parent')
        .addSelect(['parent.name'])
        .where('account.id = :id', { id })
        .andWhere('account.tenantId = :tenantId', { tenantId })
        .andWhere('account.deletedAt IS NULL')
        .getOne();

      if (!account) {
        throw new NotFoundError('Cuenta no encontrada');
      }

      return {
        ...account,
        accountTypeName: account.accountType?.name,
        accountTypeCode: account.accountType?.code,
        parentName: account.parent?.name,
      };
    } catch (error) {
      logger.error('Error finding account', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

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

      if (existing) {
        throw new ValidationError(`Ya existe una cuenta con código ${dto.code}`);
      }

      // Validate account type exists
      await this.findAccountTypeById(dto.accountTypeId);

      // Validate parent account if specified
      if (dto.parentId) {
        const parent = await this.accountRepository.findOne({
          where: {
            id: dto.parentId,
            companyId: dto.companyId,
            deletedAt: IsNull(),
          },
        });

        if (!parent) {
          throw new NotFoundError('Cuenta padre no encontrada');
        }
      }

      // Create account
      const account = this.accountRepository.create({
        tenantId,
        companyId: dto.companyId,
        code: dto.code,
        name: dto.name,
        accountTypeId: dto.accountTypeId,
        parentId: dto.parentId || null,
        currencyId: dto.currencyId || null,
        isReconcilable: dto.isReconcilable || false,
        notes: dto.notes || null,
        createdBy: userId,
      });

      await this.accountRepository.save(account);

      logger.info('Account created', {
        accountId: account.id,
        tenantId,
        code: account.code,
        createdBy: userId,
      });

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

  /**
   * Update an account
   */
  async update(
    id: string,
    dto: UpdateAccountDto,
    tenantId: string,
    userId: string
  ): Promise<Account> {
    try {
      const existing = await this.findById(id, tenantId);

      // Validate parent (prevent self-reference and cycles)
      if (dto.parentId !== undefined && dto.parentId) {
        if (dto.parentId === id) {
          throw new ValidationError('Una cuenta no puede ser su propia cuenta padre');
        }

        const parent = await this.accountRepository.findOne({
          where: {
            id: dto.parentId,
            companyId: existing.companyId,
            deletedAt: IsNull(),
          },
        });

        if (!parent) {
          throw new NotFoundError('Cuenta padre no encontrada');
        }

        // Check for circular reference
        if (await this.wouldCreateCycle(id, dto.parentId, tenantId)) {
          throw new ValidationError('La asignación crearía una referencia circular');
        }
      }

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.parentId !== undefined) existing.parentId = dto.parentId;
      if (dto.currencyId !== undefined) existing.currencyId = dto.currencyId;
      if (dto.isReconcilable !== undefined) existing.isReconcilable = dto.isReconcilable;
      if (dto.isDeprecated !== undefined) existing.isDeprecated = dto.isDeprecated;
      if (dto.notes !== undefined) existing.notes = dto.notes;

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

      await this.accountRepository.save(existing);

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

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

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

      // Check if account has children
      const childrenCount = await this.accountRepository.count({
        where: {
          parentId: id,
          deletedAt: IsNull(),
        },
      });

      if (childrenCount > 0) {
        throw new ForbiddenError('No se puede eliminar una cuenta que tiene subcuentas');
      }

      // Check if account has journal entry lines (use raw query for this check)
      const entryLinesCheck = await this.accountRepository.query(
        `SELECT COUNT(*) as count FROM financial.journal_entry_lines WHERE account_id = $1`,
        [id]
      );

      if (parseInt(entryLinesCheck[0]?.count || '0', 10) > 0) {
        throw new ForbiddenError('No se puede eliminar una cuenta que tiene movimientos contables');
      }

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

      logger.info('Account deleted', {
        accountId: id,
        tenantId,
        deletedBy: userId,
      });
    } catch (error) {
      logger.error('Error deleting account', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get account balance
   */
  async getBalance(
    accountId: string,
    tenantId: string
  ): Promise<{ debit: number; credit: number; balance: number }> {
    try {
      await this.findById(accountId, tenantId);

      const result = await this.accountRepository.query(
        `SELECT COALESCE(SUM(jel.debit), 0) as total_debit,
                COALESCE(SUM(jel.credit), 0) as total_credit
         FROM financial.journal_entry_lines jel
         INNER JOIN financial.journal_entries je ON jel.entry_id = je.id
         WHERE jel.account_id = $1 AND je.status = 'posted'`,
        [accountId]
      );

      const debit = parseFloat(result[0]?.total_debit || '0');
      const credit = parseFloat(result[0]?.total_credit || '0');

      return {
        debit,
        credit,
        balance: debit - credit,
      };
    } catch (error) {
      logger.error('Error getting account balance', {
        error: (error as Error).message,
        accountId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Check if assigning a parent would create a circular reference
   */
  private async wouldCreateCycle(
    accountId: string,
    newParentId: string,
    tenantId: string
  ): Promise<boolean> {
    let currentId: string | null = newParentId;
    const visited = new Set<string>();

    while (currentId) {
      if (visited.has(currentId)) {
        return true; // Found a cycle
      }
      if (currentId === accountId) {
        return true; // Would create a cycle
      }

      visited.add(currentId);

      const parent = await this.accountRepository.findOne({
        where: { id: currentId, tenantId, deletedAt: IsNull() },
        select: ['parentId'],
      });

      currentId = parent?.parentId || null;
    }

    return false;
  }
}

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

export const accountsService = new AccountsService();
