/**
 * DashboardService - Gestión de Dashboards
 *
 * Administra dashboards personalizables y sus widgets.
 *
 * @module Reports
 */

import { Repository, DataSource } from 'typeorm';
import { Dashboard, DashboardType, DashboardVisibility } from '../entities/dashboard.entity';
import { DashboardWidget, WidgetType, DataSourceType } from '../entities/dashboard-widget.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

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

export interface CreateDashboardDto {
  code: string;
  name: string;
  description?: string;
  dashboardType?: DashboardType;
  visibility?: DashboardVisibility;
  fraccionamientoId?: string;
  layout?: Record<string, any>;
  theme?: Record<string, any>;
  refreshInterval?: number;
  defaultDateRange?: string;
  defaultFilters?: Record<string, any>;
  allowedRoles?: string[];
}

export interface UpdateDashboardDto extends Partial<CreateDashboardDto> {
  isDefault?: boolean;
  isActive?: boolean;
  sortOrder?: number;
}

export interface CreateWidgetDto {
  dashboardId: string;
  title: string;
  subtitle?: string;
  widgetType: WidgetType;
  dataSourceType?: DataSourceType;
  dataSource?: Record<string, any>;
  config?: Record<string, any>;
  chartOptions?: Record<string, any>;
  thresholds?: Record<string, any>;
  gridX?: number;
  gridY?: number;
  gridWidth?: number;
  gridHeight?: number;
  refreshInterval?: number;
  cacheDuration?: number;
  drillDownConfig?: Record<string, any>;
  clickAction?: Record<string, any>;
}

export interface UpdateWidgetDto extends Partial<Omit<CreateWidgetDto, 'dashboardId'>> {
  isActive?: boolean;
  sortOrder?: number;
}

export interface DashboardFilters {
  dashboardType?: DashboardType;
  visibility?: DashboardVisibility;
  ownerId?: string;
  fraccionamientoId?: string;
  isActive?: boolean;
  search?: string;
}

export class DashboardService {
  private repository: Repository<Dashboard>;
  private widgetRepository: Repository<DashboardWidget>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(Dashboard);
    this.widgetRepository = dataSource.getRepository(DashboardWidget);
  }

  /**
   * Buscar por ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<Dashboard | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });
  }

  /**
   * Crear entidad
   */
  async create(ctx: ServiceContext, data: Partial<Dashboard>): Promise<Dashboard> {
    const entity = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
    });
    return this.repository.save(entity);
  }

  /**
   * Actualizar entidad
   */
  async update(ctx: ServiceContext, id: string, data: Partial<Dashboard>): Promise<Dashboard | null> {
    await this.repository.update(
      { id, tenantId: ctx.tenantId, deletedAt: null } as any,
      data as any
    );
    return this.findById(ctx, id);
  }

  /**
   * Soft delete
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.update(
      { id, tenantId: ctx.tenantId, deletedAt: null } as any,
      { deletedAt: new Date() } as any
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Buscar dashboards con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: DashboardFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Dashboard>> {
    const qb = this.repository
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.widgets', 'w', 'w.deleted_at IS NULL AND w.is_active = true')
      .where('d.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('d.deleted_at IS NULL');

    if (filters.dashboardType) {
      qb.andWhere('d.dashboard_type = :dashboardType', { dashboardType: filters.dashboardType });
    }
    if (filters.visibility) {
      qb.andWhere('d.visibility = :visibility', { visibility: filters.visibility });
    }
    if (filters.ownerId) {
      qb.andWhere('d.owner_id = :ownerId', { ownerId: filters.ownerId });
    }
    if (filters.fraccionamientoId) {
      qb.andWhere('d.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId: filters.fraccionamientoId });
    }
    if (filters.isActive !== undefined) {
      qb.andWhere('d.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.search) {
      qb.andWhere('(d.name ILIKE :search OR d.code ILIKE :search OR d.description ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('d.sort_order', 'ASC')
      .addOrderBy('d.name', 'ASC')
      .skip(skip)
      .take(limit);

    const [data, total] = await qb.getManyAndCount();

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

  /**
   * Buscar dashboard por código
   */
  async findByCode(ctx: ServiceContext, code: string): Promise<Dashboard | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        code,
        deletedAt: null,
      } as any,
      relations: ['widgets'],
    });
  }

  /**
   * Obtener dashboard con widgets
   */
  async findByIdWithWidgets(ctx: ServiceContext, id: string): Promise<Dashboard | null> {
    return this.repository
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.widgets', 'w', 'w.deleted_at IS NULL')
      .where('d.id = :id', { id })
      .andWhere('d.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('d.deleted_at IS NULL')
      .orderBy('w.sort_order', 'ASC')
      .getOne();
  }

  /**
   * Obtener dashboards por tipo
   */
  async findByType(ctx: ServiceContext, dashboardType: DashboardType): Promise<Dashboard[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        dashboardType,
        isActive: true,
        deletedAt: null,
      } as any,
      order: { sortOrder: 'ASC', name: 'ASC' },
    });
  }

  /**
   * Obtener dashboards del usuario
   */
  async findByOwner(ctx: ServiceContext, ownerId: string): Promise<Dashboard[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        ownerId,
        deletedAt: null,
      } as any,
      order: { sortOrder: 'ASC', name: 'ASC' },
    });
  }

  /**
   * Obtener dashboard por defecto por tipo
   */
  async findDefault(ctx: ServiceContext, dashboardType: DashboardType): Promise<Dashboard | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        dashboardType,
        isDefault: true,
        isActive: true,
        deletedAt: null,
      } as any,
      relations: ['widgets'],
    });
  }

  /**
   * Crear dashboard
   */
  async createDashboard(ctx: ServiceContext, data: CreateDashboardDto): Promise<Dashboard> {
    const existing = await this.findByCode(ctx, data.code);
    if (existing) {
      throw new Error(`Dashboard with code ${data.code} already exists`);
    }

    return this.create(ctx, {
      ...data,
      ownerId: ctx.userId,
      isActive: true,
      isSystem: false,
      isDefault: false,
      viewCount: 0,
    });
  }

  /**
   * Establecer dashboard por defecto
   */
  async setDefault(ctx: ServiceContext, id: string): Promise<Dashboard | null> {
    const dashboard = await this.findById(ctx, id);
    if (!dashboard) {
      return null;
    }

    // Quitar default de otros dashboards del mismo tipo
    await this.repository.update(
      {
        tenantId: ctx.tenantId,
        dashboardType: dashboard.dashboardType,
        isDefault: true,
      } as any,
      { isDefault: false } as any
    );

    return this.update(ctx, id, { isDefault: true });
  }

  /**
   * Registrar vista de dashboard
   */
  async recordView(ctx: ServiceContext, id: string): Promise<void> {
    await this.repository.update(
      { id, tenantId: ctx.tenantId } as any,
      {
        viewCount: () => 'view_count + 1',
        lastViewedAt: new Date(),
      } as any
    );
  }

  /**
   * Duplicar dashboard
   */
  async duplicate(
    ctx: ServiceContext,
    id: string,
    newCode: string,
    newName: string
  ): Promise<Dashboard> {
    const original = await this.findByIdWithWidgets(ctx, id);
    if (!original) {
      throw new Error('Dashboard not found');
    }

    // Crear copia del dashboard
    const newDashboard = await this.create(ctx, {
      code: newCode,
      name: newName,
      description: original.description,
      dashboardType: original.dashboardType,
      visibility: 'private',
      layout: original.layout,
      theme: original.theme,
      refreshInterval: original.refreshInterval,
      defaultDateRange: original.defaultDateRange,
      defaultFilters: original.defaultFilters,
      ownerId: ctx.userId,
      isDefault: false,
      isSystem: false,
      isActive: true,
    });

    // Copiar widgets
    if (original.widgets?.length) {
      for (const widget of original.widgets) {
        await this.createWidget(ctx, {
          dashboardId: newDashboard.id,
          title: widget.title,
          subtitle: widget.subtitle || undefined,
          widgetType: widget.widgetType,
          dataSourceType: widget.dataSourceType,
          dataSource: widget.dataSource || undefined,
          config: widget.config || undefined,
          chartOptions: widget.chartOptions || undefined,
          thresholds: widget.thresholds || undefined,
          gridX: widget.gridX,
          gridY: widget.gridY,
          gridWidth: widget.gridWidth,
          gridHeight: widget.gridHeight,
          refreshInterval: widget.refreshInterval || undefined,
          cacheDuration: widget.cacheDuration,
          drillDownConfig: widget.drillDownConfig || undefined,
          clickAction: widget.clickAction || undefined,
        });
      }
    }

    return this.findByIdWithWidgets(ctx, newDashboard.id) as Promise<Dashboard>;
  }

  // ==================== Widget Methods ====================

  /**
   * Crear widget
   */
  async createWidget(ctx: ServiceContext, data: CreateWidgetDto): Promise<DashboardWidget> {
    const dashboard = await this.findById(ctx, data.dashboardId);
    if (!dashboard) {
      throw new Error('Dashboard not found');
    }

    const widget = this.widgetRepository.create({
      tenantId: ctx.tenantId,
      ...data,
      isActive: true,
      createdById: ctx.userId,
    });

    return this.widgetRepository.save(widget);
  }

  /**
   * Actualizar widget
   */
  async updateWidget(
    ctx: ServiceContext,
    widgetId: string,
    data: UpdateWidgetDto
  ): Promise<DashboardWidget | null> {
    const widget = await this.widgetRepository.findOne({
      where: {
        id: widgetId,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });

    if (!widget) {
      return null;
    }

    const updated = this.widgetRepository.merge(widget, {
      ...data,
      updatedById: ctx.userId,
    });

    return this.widgetRepository.save(updated);
  }

  /**
   * Eliminar widget (soft delete)
   */
  async deleteWidget(ctx: ServiceContext, widgetId: string): Promise<boolean> {
    const result = await this.widgetRepository.update(
      {
        id: widgetId,
        tenantId: ctx.tenantId,
      } as any,
      {
        deletedAt: new Date(),
      } as any
    );

    return (result.affected ?? 0) > 0;
  }

  /**
   * Obtener widgets de un dashboard
   */
  async getWidgets(ctx: ServiceContext, dashboardId: string): Promise<DashboardWidget[]> {
    return this.widgetRepository.find({
      where: {
        tenantId: ctx.tenantId,
        dashboardId,
        deletedAt: null,
      } as any,
      order: { sortOrder: 'ASC' },
    });
  }

  /**
   * Actualizar posiciones de widgets
   */
  async updateWidgetPositions(
    ctx: ServiceContext,
    dashboardId: string,
    positions: { id: string; gridX: number; gridY: number; gridWidth: number; gridHeight: number }[]
  ): Promise<void> {
    for (const pos of positions) {
      await this.widgetRepository.update(
        {
          id: pos.id,
          dashboardId,
          tenantId: ctx.tenantId,
        } as any,
        {
          gridX: pos.gridX,
          gridY: pos.gridY,
          gridWidth: pos.gridWidth,
          gridHeight: pos.gridHeight,
        } as any
      );
    }
  }

  /**
   * Estadísticas de dashboards
   */
  async getStats(ctx: ServiceContext): Promise<DashboardStats> {
    const dashboards = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });

    const byType = new Map<DashboardType, number>();
    let activeCount = 0;
    let totalViews = 0;

    dashboards.forEach((d) => {
      byType.set(d.dashboardType, (byType.get(d.dashboardType) || 0) + 1);
      if (d.isActive) activeCount++;
      totalViews += d.viewCount;
    });

    const widgetCount = await this.widgetRepository.count({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });

    return {
      totalDashboards: dashboards.length,
      activeDashboards: activeCount,
      totalWidgets: widgetCount,
      totalViews,
      byType: Array.from(byType.entries()).map(([type, count]) => ({ type, count })),
    };
  }
}

export interface DashboardStats {
  totalDashboards: number;
  activeDashboards: number;
  totalWidgets: number;
  totalViews: number;
  byType: { type: DashboardType; count: number }[];
}
