/**
 * Depreciation Service
 * ERP Construccion - Modulo Assets (MAE-015)
 *
 * Logica de negocio para depreciacion de activos fijos.
 * @gap GAP-003
 */

import { Repository, DataSource } from 'typeorm';
import { DepreciationSchedule, DepreciationMethod, AssetDepreciationType } from '../entities/depreciation-schedule.entity';
import { DepreciationEntry } from '../entities/depreciation-entry.entity';
import { Asset } from '../entities/asset.entity';

// DTOs
export interface CreateDepreciationScheduleDto {
  assetId: string;
  assetType: AssetDepreciationType;
  method?: DepreciationMethod;
  originalValue: number;
  salvageValue?: number;
  usefulLifeMonths: number;
  usefulLifeUnits?: number;
  depreciationStartDate: Date;
  notes?: string;
}

export interface UpdateDepreciationScheduleDto {
  method?: DepreciationMethod;
  salvageValue?: number;
  usefulLifeMonths?: number;
  usefulLifeUnits?: number;
  depreciationEndDate?: Date;
  isActive?: boolean;
  notes?: string;
}

export interface CreateDepreciationEntryDto {
  scheduleId: string;
  periodDate: Date;
  depreciationAmount: number;
  unitsUsed?: number;
  notes?: string;
}

export interface DepreciationScheduleFilters {
  assetType?: AssetDepreciationType;
  method?: DepreciationMethod;
  isActive?: boolean;
  isFullyDepreciated?: boolean;
}

// Re-use shared interfaces
interface PaginationOptions {
  page: number;
  limit: number;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export class DepreciationService {
  private scheduleRepository: Repository<DepreciationSchedule>;
  private entryRepository: Repository<DepreciationEntry>;
  private assetRepository: Repository<Asset>;

  constructor(dataSource: DataSource) {
    this.scheduleRepository = dataSource.getRepository(DepreciationSchedule);
    this.entryRepository = dataSource.getRepository(DepreciationEntry);
    this.assetRepository = dataSource.getRepository(Asset);
  }

  // ============================================
  // DEPRECIATION SCHEDULES
  // ============================================

  async createSchedule(tenantId: string, dto: CreateDepreciationScheduleDto, userId?: string): Promise<DepreciationSchedule> {
    // Verify asset exists
    const asset = await this.assetRepository.findOne({
      where: { id: dto.assetId, tenantId },
    });

    if (!asset) {
      throw new Error('Asset not found');
    }

    // Check if asset already has a depreciation schedule
    const existingSchedule = await this.scheduleRepository.findOne({
      where: { tenantId, assetId: dto.assetId, isActive: true },
    });

    if (existingSchedule) {
      throw new Error('Asset already has an active depreciation schedule');
    }

    const salvageValue = dto.salvageValue ?? 0;
    const currentBookValue = dto.originalValue;

    const schedule = this.scheduleRepository.create({
      tenantId,
      assetId: dto.assetId,
      assetType: dto.assetType,
      method: dto.method || 'straight_line',
      originalValue: dto.originalValue,
      salvageValue,
      usefulLifeMonths: dto.usefulLifeMonths,
      usefulLifeUnits: dto.usefulLifeUnits,
      depreciationStartDate: dto.depreciationStartDate,
      accumulatedDepreciation: 0,
      currentBookValue,
      isActive: true,
      isFullyDepreciated: false,
      notes: dto.notes,
      createdBy: userId,
    });

    const savedSchedule = await this.scheduleRepository.save(schedule);

    // Update asset depreciation info
    await this.assetRepository.update(
      { id: dto.assetId, tenantId },
      {
        depreciationMethod: dto.method || 'straight_line',
        usefulLifeYears: Math.ceil(dto.usefulLifeMonths / 12),
        salvageValue,
        currentBookValue,
        accumulatedDepreciation: 0,
      }
    );

    return savedSchedule;
  }

  async findScheduleById(tenantId: string, id: string): Promise<DepreciationSchedule | null> {
    return this.scheduleRepository.findOne({
      where: { id, tenantId },
      relations: ['asset', 'entries'],
    });
  }

  async findScheduleByAsset(tenantId: string, assetId: string): Promise<DepreciationSchedule | null> {
    return this.scheduleRepository.findOne({
      where: { tenantId, assetId, isActive: true },
      relations: ['entries'],
    });
  }

  async findAllSchedules(
    tenantId: string,
    filters: DepreciationScheduleFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<DepreciationSchedule>> {
    const queryBuilder = this.scheduleRepository.createQueryBuilder('schedule')
      .leftJoinAndSelect('schedule.asset', 'asset')
      .where('schedule.tenant_id = :tenantId', { tenantId });

    if (filters.assetType) {
      queryBuilder.andWhere('schedule.asset_type = :assetType', { assetType: filters.assetType });
    }
    if (filters.method) {
      queryBuilder.andWhere('schedule.method = :method', { method: filters.method });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('schedule.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.isFullyDepreciated !== undefined) {
      queryBuilder.andWhere('schedule.is_fully_depreciated = :isFullyDepreciated', { isFullyDepreciated: filters.isFullyDepreciated });
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .orderBy('schedule.created_at', 'DESC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  async updateSchedule(tenantId: string, id: string, dto: UpdateDepreciationScheduleDto, userId?: string): Promise<DepreciationSchedule | null> {
    const schedule = await this.findScheduleById(tenantId, id);
    if (!schedule) return null;

    Object.assign(schedule, dto, { updatedBy: userId });
    return this.scheduleRepository.save(schedule);
  }

  // ============================================
  // DEPRECIATION ENTRIES
  // ============================================

  async createEntry(tenantId: string, dto: CreateDepreciationEntryDto, userId?: string): Promise<DepreciationEntry> {
    const schedule = await this.findScheduleById(tenantId, dto.scheduleId);
    if (!schedule) {
      throw new Error('Depreciation schedule not found');
    }

    if (!schedule.isActive || schedule.isFullyDepreciated) {
      throw new Error('Schedule is not active or already fully depreciated');
    }

    // Calculate new accumulated depreciation and book value
    const accumulatedDepreciation = schedule.accumulatedDepreciation + dto.depreciationAmount;
    const depreciableAmount = schedule.originalValue - schedule.salvageValue;
    const bookValue = schedule.originalValue - accumulatedDepreciation;

    // Check if exceeds depreciable amount
    if (accumulatedDepreciation > depreciableAmount) {
      throw new Error('Depreciation amount exceeds remaining depreciable value');
    }

    const periodDate = new Date(dto.periodDate);
    const fiscalYear = periodDate.getFullYear();
    const fiscalMonth = periodDate.getMonth() + 1;

    const entry = this.entryRepository.create({
      tenantId,
      scheduleId: dto.scheduleId,
      periodDate: dto.periodDate,
      fiscalYear,
      fiscalMonth,
      depreciationAmount: dto.depreciationAmount,
      accumulatedDepreciation,
      bookValue,
      unitsUsed: dto.unitsUsed,
      status: 'draft',
      notes: dto.notes,
      createdBy: userId,
    });

    const savedEntry = await this.entryRepository.save(entry);

    // Update schedule totals
    const isFullyDepreciated = accumulatedDepreciation >= depreciableAmount;
    await this.scheduleRepository.update(
      { id: dto.scheduleId, tenantId },
      {
        accumulatedDepreciation,
        currentBookValue: bookValue,
        lastEntryDate: dto.periodDate,
        isFullyDepreciated,
        updatedBy: userId,
      }
    );

    // Update asset book value
    await this.assetRepository.update(
      { id: schedule.assetId, tenantId },
      {
        currentBookValue: bookValue,
        accumulatedDepreciation,
        lastDepreciationDate: dto.periodDate,
      }
    );

    return savedEntry;
  }

  async findEntryById(tenantId: string, id: string): Promise<DepreciationEntry | null> {
    return this.entryRepository.findOne({
      where: { id, tenantId },
      relations: ['schedule'],
    });
  }

  async findEntriesBySchedule(
    tenantId: string,
    scheduleId: string,
    pagination: PaginationOptions = { page: 1, limit: 50 }
  ): Promise<PaginatedResult<DepreciationEntry>> {
    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await this.entryRepository.findAndCount({
      where: { tenantId, scheduleId },
      order: { periodDate: 'DESC' },
      skip,
      take: pagination.limit,
    });

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  async postEntry(tenantId: string, id: string, journalEntryId?: string, userId?: string): Promise<DepreciationEntry | null> {
    const entry = await this.findEntryById(tenantId, id);
    if (!entry) return null;

    if (entry.status !== 'draft') {
      throw new Error('Entry is not in draft status');
    }

    entry.status = 'posted';
    entry.isPosted = true;
    entry.postedAt = new Date();
    entry.postedBy = userId;
    entry.journalEntryId = journalEntryId;

    return this.entryRepository.save(entry);
  }

  // ============================================
  // CALCULATIONS
  // ============================================

  calculateMonthlyDepreciation(schedule: DepreciationSchedule): number {
    const depreciableAmount = schedule.originalValue - schedule.salvageValue;

    switch (schedule.method) {
      case 'straight_line':
        return depreciableAmount / schedule.usefulLifeMonths;

      case 'declining_balance':
        const remainingValue = schedule.originalValue - schedule.accumulatedDepreciation;
        const rate = 1 / schedule.usefulLifeMonths;
        return remainingValue * rate;

      case 'double_declining':
        const remainingValue2 = schedule.originalValue - schedule.accumulatedDepreciation;
        const rate2 = (2 / schedule.usefulLifeMonths);
        return Math.max(remainingValue2 * rate2, 0);

      case 'sum_of_years':
        const n = Math.ceil(schedule.usefulLifeMonths / 12);
        const sumOfYears = (n * (n + 1)) / 2;
        const currentYear = Math.ceil(
          (new Date().getTime() - new Date(schedule.depreciationStartDate).getTime()) / (365.25 * 24 * 60 * 60 * 1000)
        ) + 1;
        const remainingYears = n - currentYear + 1;
        return (depreciableAmount * remainingYears) / (sumOfYears * 12);

      case 'units_of_production':
        if (!schedule.usefulLifeUnits) return 0;
        return depreciableAmount / schedule.usefulLifeUnits;

      default:
        return depreciableAmount / schedule.usefulLifeMonths;
    }
  }

  async generateMonthlyEntry(tenantId: string, scheduleId: string, periodDate: Date, unitsUsed?: number, userId?: string): Promise<DepreciationEntry> {
    const schedule = await this.findScheduleById(tenantId, scheduleId);
    if (!schedule) {
      throw new Error('Schedule not found');
    }

    let depreciationAmount = this.calculateMonthlyDepreciation(schedule);

    // For units of production, calculate based on units used
    if (schedule.method === 'units_of_production' && unitsUsed && schedule.usefulLifeUnits) {
      const depreciableAmount = schedule.originalValue - schedule.salvageValue;
      depreciationAmount = (depreciableAmount / schedule.usefulLifeUnits) * unitsUsed;
    }

    // Don't exceed remaining depreciable amount
    const remainingDepreciable = (schedule.originalValue - schedule.salvageValue) - schedule.accumulatedDepreciation;
    depreciationAmount = Math.min(depreciationAmount, remainingDepreciable);

    return this.createEntry(tenantId, {
      scheduleId,
      periodDate,
      depreciationAmount: Math.round(depreciationAmount * 100) / 100,
      unitsUsed,
    }, userId);
  }

  // ============================================
  // REPORTS
  // ============================================

  async getDepreciationSummary(tenantId: string): Promise<{
    totalOriginalValue: number;
    totalAccumulatedDepreciation: number;
    totalBookValue: number;
    activeSchedules: number;
    fullyDepreciated: number;
  }> {
    const result = await this.scheduleRepository
      .createQueryBuilder('schedule')
      .select('SUM(schedule.original_value)', 'totalOriginalValue')
      .addSelect('SUM(schedule.accumulated_depreciation)', 'totalAccumulatedDepreciation')
      .addSelect('SUM(schedule.current_book_value)', 'totalBookValue')
      .addSelect('COUNT(CASE WHEN schedule.is_active = true THEN 1 END)', 'activeSchedules')
      .addSelect('COUNT(CASE WHEN schedule.is_fully_depreciated = true THEN 1 END)', 'fullyDepreciated')
      .where('schedule.tenant_id = :tenantId', { tenantId })
      .getRawOne();

    return {
      totalOriginalValue: parseFloat(result.totalOriginalValue) || 0,
      totalAccumulatedDepreciation: parseFloat(result.totalAccumulatedDepreciation) || 0,
      totalBookValue: parseFloat(result.totalBookValue) || 0,
      activeSchedules: parseInt(result.activeSchedules) || 0,
      fullyDepreciated: parseInt(result.fullyDepreciated) || 0,
    };
  }

  async getPendingDepreciations(tenantId: string, asOfDate: Date): Promise<DepreciationSchedule[]> {
    return this.scheduleRepository
      .createQueryBuilder('schedule')
      .leftJoinAndSelect('schedule.asset', 'asset')
      .where('schedule.tenant_id = :tenantId', { tenantId })
      .andWhere('schedule.is_active = true')
      .andWhere('schedule.is_fully_depreciated = false')
      .andWhere('(schedule.last_entry_date IS NULL OR schedule.last_entry_date < :asOfDate)', { asOfDate })
      .orderBy('schedule.depreciation_start_date', 'ASC')
      .getMany();
  }
}
