/**
 * Asset Service
 * ERP Construccion - Modulo Activos (MAE-015)
 *
 * Logica de negocio para gestion de activos fijos y maquinaria.
 */

import { Repository, DataSource, IsNull } from 'typeorm';
import { Asset, AssetType, AssetStatus, OwnershipType } from '../entities/asset.entity';
import { AssetCategory } from '../entities/asset-category.entity';
import { AssetAssignment } from '../entities/asset-assignment.entity';

// DTOs
export interface CreateAssetDto {
  assetCode: string;
  name: string;
  description?: string;
  categoryId?: string;
  assetType: AssetType;
  ownershipType?: OwnershipType;
  brand?: string;
  model?: string;
  serialNumber?: string;
  yearManufactured?: number;
  specifications?: Record<string, any>;
  capacity?: string;
  powerRating?: string;
  fuelType?: string;
  fuelCapacity?: number;
  purchaseDate?: Date;
  purchasePrice?: number;
  supplierId?: string;
  usefulLifeYears?: number;
  salvageValue?: number;
  photoUrl?: string;
  notes?: string;
  tags?: string[];
}

export interface UpdateAssetDto extends Partial<CreateAssetDto> {
  status?: AssetStatus;
  currentProjectId?: string;
  currentLocationName?: string;
  currentLatitude?: number;
  currentLongitude?: number;
  currentHours?: number;
  currentKilometers?: number;
  assignedOperatorId?: string;
  lastLocationUpdate?: Date;
  lastUsageUpdate?: Date;
}

export interface AssetFilters {
  assetType?: AssetType;
  status?: AssetStatus;
  ownershipType?: OwnershipType;
  categoryId?: string;
  projectId?: string;
  search?: string;
  tags?: string[];
}

export interface AssignAssetDto {
  assetId: string;
  projectId: string;
  projectCode?: string;
  projectName?: string;
  startDate: Date;
  operatorId?: string;
  operatorName?: string;
  responsibleId?: string;
  responsibleName?: string;
  locationInProject?: string;
  dailyRate?: number;
  hourlyRate?: number;
  transferReason?: string;
}

export interface PaginationOptions {
  page: number;
  limit: number;
}

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

export class AssetService {
  private assetRepository: Repository<Asset>;
  private categoryRepository: Repository<AssetCategory>;
  private assignmentRepository: Repository<AssetAssignment>;

  constructor(dataSource: DataSource) {
    this.assetRepository = dataSource.getRepository(Asset);
    this.categoryRepository = dataSource.getRepository(AssetCategory);
    this.assignmentRepository = dataSource.getRepository(AssetAssignment);
  }

  // ============================================
  // ASSETS
  // ============================================

  /**
   * Create a new asset
   */
  async create(tenantId: string, dto: CreateAssetDto, userId?: string): Promise<Asset> {
    // Check code uniqueness
    const existing = await this.assetRepository.findOne({
      where: { tenantId, assetCode: dto.assetCode },
    });

    if (existing) {
      throw new Error(`Asset with code ${dto.assetCode} already exists`);
    }

    const asset = this.assetRepository.create({
      tenantId,
      ...dto,
      status: 'available' as AssetStatus,
      ownershipType: dto.ownershipType || ('owned' as OwnershipType),
      currentBookValue: dto.purchasePrice,
      createdBy: userId,
    });

    return this.assetRepository.save(asset);
  }

  /**
   * Find asset by ID
   */
  async findById(tenantId: string, id: string): Promise<Asset | null> {
    return this.assetRepository.findOne({
      where: { id, tenantId },
      relations: ['category'],
    });
  }

  /**
   * Find asset by code
   */
  async findByCode(tenantId: string, code: string): Promise<Asset | null> {
    return this.assetRepository.findOne({
      where: { tenantId, assetCode: code },
      relations: ['category'],
    });
  }

  /**
   * List assets with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: AssetFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<Asset>> {
    const queryBuilder = this.assetRepository.createQueryBuilder('asset')
      .leftJoinAndSelect('asset.category', 'category')
      .where('asset.tenant_id = :tenantId', { tenantId })
      .andWhere('asset.deleted_at IS NULL');

    if (filters.assetType) {
      queryBuilder.andWhere('asset.asset_type = :assetType', { assetType: filters.assetType });
    }
    if (filters.status) {
      queryBuilder.andWhere('asset.status = :status', { status: filters.status });
    }
    if (filters.ownershipType) {
      queryBuilder.andWhere('asset.ownership_type = :ownershipType', { ownershipType: filters.ownershipType });
    }
    if (filters.categoryId) {
      queryBuilder.andWhere('asset.category_id = :categoryId', { categoryId: filters.categoryId });
    }
    if (filters.projectId) {
      queryBuilder.andWhere('asset.current_project_id = :projectId', { projectId: filters.projectId });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(asset.asset_code ILIKE :search OR asset.name ILIKE :search OR asset.serial_number ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }
    if (filters.tags && filters.tags.length > 0) {
      queryBuilder.andWhere('asset.tags && :tags', { tags: filters.tags });
    }

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

    const [data, total] = await queryBuilder
      .orderBy('asset.name', 'ASC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

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

  /**
   * Update asset
   */
  async update(tenantId: string, id: string, dto: UpdateAssetDto, userId?: string): Promise<Asset | null> {
    const asset = await this.findById(tenantId, id);
    if (!asset) return null;

    // Update location timestamp if location changed
    if (dto.currentLatitude !== undefined || dto.currentLongitude !== undefined) {
      dto.lastLocationUpdate = new Date();
    }

    // Update usage timestamp if hours/km changed
    if (dto.currentHours !== undefined || dto.currentKilometers !== undefined) {
      dto.lastUsageUpdate = new Date();
    }

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

  /**
   * Update asset status
   */
  async updateStatus(tenantId: string, id: string, status: AssetStatus, userId?: string): Promise<Asset | null> {
    return this.update(tenantId, id, { status }, userId);
  }

  /**
   * Update asset usage metrics (hours, km)
   */
  async updateUsage(
    tenantId: string,
    id: string,
    hours?: number,
    kilometers?: number,
    userId?: string
  ): Promise<Asset | null> {
    const asset = await this.findById(tenantId, id);
    if (!asset) return null;

    if (hours !== undefined) {
      asset.currentHours = hours;
    }
    if (kilometers !== undefined) {
      asset.currentKilometers = kilometers;
    }
    asset.lastUsageUpdate = new Date();
    asset.updatedBy = userId;

    return this.assetRepository.save(asset);
  }

  /**
   * Soft delete asset
   */
  async delete(tenantId: string, id: string, userId?: string): Promise<boolean> {
    const result = await this.assetRepository.update(
      { id, tenantId },
      { deletedAt: new Date(), status: 'retired' as AssetStatus, updatedBy: userId }
    );
    return (result.affected ?? 0) > 0;
  }

  // ============================================
  // ASSIGNMENTS
  // ============================================

  /**
   * Assign asset to project
   */
  async assignToProject(tenantId: string, dto: AssignAssetDto, userId?: string): Promise<AssetAssignment> {
    // Close current assignment if exists
    await this.assignmentRepository.update(
      { tenantId, assetId: dto.assetId, isCurrent: true },
      { isCurrent: false, endDate: new Date() }
    );

    // Get asset for current metrics
    const asset = await this.findById(tenantId, dto.assetId);
    if (!asset) {
      throw new Error('Asset not found');
    }

    // Create new assignment
    const assignment = this.assignmentRepository.create({
      tenantId,
      assetId: dto.assetId,
      projectId: dto.projectId,
      projectCode: dto.projectCode,
      projectName: dto.projectName,
      startDate: dto.startDate,
      isCurrent: true,
      operatorId: dto.operatorId,
      operatorName: dto.operatorName,
      responsibleId: dto.responsibleId,
      responsibleName: dto.responsibleName,
      locationInProject: dto.locationInProject,
      hoursAtStart: asset.currentHours,
      kilometersAtStart: asset.currentKilometers,
      dailyRate: dto.dailyRate,
      hourlyRate: dto.hourlyRate,
      transferReason: dto.transferReason,
      createdBy: userId,
    });

    const savedAssignment = await this.assignmentRepository.save(assignment);

    // Update asset with new project
    await this.update(tenantId, dto.assetId, {
      currentProjectId: dto.projectId,
      status: 'assigned' as AssetStatus,
      assignedOperatorId: dto.operatorId,
    }, userId);

    return savedAssignment;
  }

  /**
   * Get current assignment for asset
   */
  async getCurrentAssignment(tenantId: string, assetId: string): Promise<AssetAssignment | null> {
    return this.assignmentRepository.findOne({
      where: { tenantId, assetId, isCurrent: true },
    });
  }

  /**
   * Get assignment history for asset
   */
  async getAssignmentHistory(
    tenantId: string,
    assetId: string,
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<AssetAssignment>> {
    const skip = (pagination.page - 1) * pagination.limit;

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

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

  /**
   * Return asset from project (close assignment)
   */
  async returnFromProject(tenantId: string, assetId: string, endDate: Date, userId?: string): Promise<boolean> {
    const assignment = await this.getCurrentAssignment(tenantId, assetId);
    if (!assignment) return false;

    const asset = await this.findById(tenantId, assetId);
    if (!asset) return false;

    // Close assignment
    assignment.isCurrent = false;
    assignment.endDate = endDate;
    assignment.hoursAtEnd = asset.currentHours;
    assignment.kilometersAtEnd = asset.currentKilometers;
    assignment.updatedBy = userId;
    await this.assignmentRepository.save(assignment);

    // Update asset status
    await this.update(tenantId, assetId, {
      currentProjectId: undefined,
      status: 'available' as AssetStatus,
    }, userId);

    return true;
  }

  // ============================================
  // CATEGORIES
  // ============================================

  /**
   * Create category
   */
  async createCategory(tenantId: string, data: Partial<AssetCategory>, userId?: string): Promise<AssetCategory> {
    const category = this.categoryRepository.create({
      tenantId,
      ...data,
      createdBy: userId,
    });
    return this.categoryRepository.save(category);
  }

  /**
   * Get all categories
   */
  async getCategories(tenantId: string): Promise<AssetCategory[]> {
    return this.categoryRepository.find({
      where: { tenantId, isActive: true, deletedAt: IsNull() },
      order: { level: 'ASC', name: 'ASC' },
    });
  }

  // ============================================
  // STATISTICS
  // ============================================

  /**
   * Get asset statistics
   */
  async getStatistics(tenantId: string): Promise<{
    total: number;
    byStatus: Record<string, number>;
    byType: Record<string, number>;
    totalValue: number;
    maintenanceDue: number;
  }> {
    const [total, byStatusRaw, byTypeRaw, valueResult, maintenanceDue] = await Promise.all([
      this.assetRepository.count({ where: { tenantId, deletedAt: IsNull() } }),

      this.assetRepository.createQueryBuilder('asset')
        .select('asset.status', 'status')
        .addSelect('COUNT(*)', 'count')
        .where('asset.tenant_id = :tenantId', { tenantId })
        .andWhere('asset.deleted_at IS NULL')
        .groupBy('asset.status')
        .getRawMany(),

      this.assetRepository.createQueryBuilder('asset')
        .select('asset.asset_type', 'type')
        .addSelect('COUNT(*)', 'count')
        .where('asset.tenant_id = :tenantId', { tenantId })
        .andWhere('asset.deleted_at IS NULL')
        .groupBy('asset.asset_type')
        .getRawMany(),

      this.assetRepository.createQueryBuilder('asset')
        .select('SUM(asset.current_book_value)', 'total')
        .where('asset.tenant_id = :tenantId', { tenantId })
        .andWhere('asset.deleted_at IS NULL')
        .getRawOne(),

      this.assetRepository.createQueryBuilder('asset')
        .where('asset.tenant_id = :tenantId', { tenantId })
        .andWhere('asset.deleted_at IS NULL')
        .andWhere('asset.next_maintenance_date <= :date', { date: new Date() })
        .getCount(),
    ]);

    const byStatus: Record<string, number> = {};
    byStatusRaw.forEach((row: any) => {
      byStatus[row.status] = parseInt(row.count, 10);
    });

    const byType: Record<string, number> = {};
    byTypeRaw.forEach((row: any) => {
      byType[row.type] = parseInt(row.count, 10);
    });

    return {
      total,
      byStatus,
      byType,
      totalValue: parseFloat(valueResult?.total) || 0,
      maintenanceDue,
    };
  }

  /**
   * Get assets needing maintenance
   */
  async getAssetsNeedingMaintenance(tenantId: string): Promise<Asset[]> {
    const today = new Date();

    return this.assetRepository.createQueryBuilder('asset')
      .where('asset.tenant_id = :tenantId', { tenantId })
      .andWhere('asset.deleted_at IS NULL')
      .andWhere('asset.status != :retired', { retired: 'retired' })
      .andWhere(
        '(asset.next_maintenance_date <= :today OR asset.current_hours >= asset.next_maintenance_hours OR asset.current_kilometers >= asset.next_maintenance_kilometers)',
        { today }
      )
      .orderBy('asset.next_maintenance_date', 'ASC')
      .getMany();
  }

  /**
   * Search assets for autocomplete
   */
  async search(tenantId: string, query: string, limit = 10): Promise<Asset[]> {
    return this.assetRepository.createQueryBuilder('asset')
      .where('asset.tenant_id = :tenantId', { tenantId })
      .andWhere('asset.deleted_at IS NULL')
      .andWhere(
        '(asset.asset_code ILIKE :query OR asset.name ILIKE :query OR asset.serial_number ILIKE :query)',
        { query: `%${query}%` }
      )
      .orderBy('asset.name', 'ASC')
      .take(limit)
      .getMany();
  }
}
