/**
 * Dashboard Service
 * Manages dashboards, widgets, and widget queries
 *
 * @module Reports
 */

import { Repository, FindOptionsWhere, ILike, IsNull } from 'typeorm';
import {
  Dashboard,
  DashboardType,
  DashboardVisibility,
} from '../entities/dashboard.entity';
import { DashboardWidget, WidgetType, DataSourceType } from '../entities/dashboard-widget.entity';
import { WidgetQuery } from '../entities/widget-query.entity';

export interface DashboardSearchParams {
  tenantId: string;
  search?: string;
  dashboardType?: DashboardType;
  visibility?: DashboardVisibility;
  ownerId?: string;
  fraccionamientoId?: string;
  isActive?: boolean;
  limit?: number;
  offset?: number;
}

export interface CreateDashboardDto {
  code: string;
  name: string;
  description?: string | null;
  dashboardType?: DashboardType;
  visibility?: DashboardVisibility;
  ownerId?: string | null;
  fraccionamientoId?: string | null;
  layout?: Record<string, any> | null;
  theme?: Record<string, any> | null;
  refreshInterval?: number;
  defaultDateRange?: string;
  defaultFilters?: Record<string, any> | null;
  allowedRoles?: string[] | null;
  isDefault?: boolean;
  isActive?: boolean;
  isSystem?: boolean;
  sortOrder?: number;
}

export interface UpdateDashboardDto extends Partial<CreateDashboardDto> {}

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

export interface UpdateWidgetDto extends Partial<Omit<CreateWidgetDto, 'dashboardId'>> {}

export interface CreateWidgetQueryDto {
  widgetId: string;
  name: string;
  queryText?: string | null;
  queryFunction?: string | null;
  parameters?: Record<string, any>;
  resultMapping?: Record<string, any>;
  cacheTtlSeconds?: number | null;
}

export interface UpdateWidgetQueryDto extends Partial<Omit<CreateWidgetQueryDto, 'widgetId'>> {}

export class DashboardService {
  constructor(
    private readonly dashboardRepository: Repository<Dashboard>,
    private readonly widgetRepository: Repository<DashboardWidget>,
    private readonly widgetQueryRepository: Repository<WidgetQuery>
  ) {}

  // ==================== Dashboard CRUD ====================

  async findAll(params: DashboardSearchParams): Promise<{ data: Dashboard[]; total: number }> {
    const {
      tenantId,
      search,
      dashboardType,
      visibility,
      ownerId,
      fraccionamientoId,
      isActive,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<Dashboard>[] = [];
    const baseWhere: FindOptionsWhere<Dashboard> = {
      tenantId,
      deletedAt: IsNull(),
    };

    if (dashboardType) {
      baseWhere.dashboardType = dashboardType;
    }

    if (visibility) {
      baseWhere.visibility = visibility;
    }

    if (ownerId) {
      baseWhere.ownerId = ownerId;
    }

    if (fraccionamientoId) {
      baseWhere.fraccionamientoId = fraccionamientoId;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) },
        { ...baseWhere, description: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.dashboardRepository.findAndCount({
      where,
      relations: ['widgets'],
      take: limit,
      skip: offset,
      order: { sortOrder: 'ASC', name: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<Dashboard | null> {
    return this.dashboardRepository.findOne({
      where: { id, tenantId, deletedAt: IsNull() },
      relations: ['widgets'],
    });
  }

  async findByCode(code: string, tenantId: string): Promise<Dashboard | null> {
    return this.dashboardRepository.findOne({
      where: { code, tenantId, deletedAt: IsNull() },
      relations: ['widgets'],
    });
  }

  async create(
    tenantId: string,
    dto: CreateDashboardDto,
    createdById?: string
  ): Promise<Dashboard> {
    // Check for existing code
    const existingCode = await this.findByCode(dto.code, tenantId);
    if (existingCode) {
      throw new Error('A dashboard with this code already exists');
    }

    const dashboard = this.dashboardRepository.create({
      ...dto,
      tenantId,
      createdById,
    });

    return this.dashboardRepository.save(dashboard);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdateDashboardDto,
    updatedById?: string
  ): Promise<Dashboard | null> {
    const dashboard = await this.findOne(id, tenantId);
    if (!dashboard) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== dashboard.code) {
      const existing = await this.findByCode(dto.code, tenantId);
      if (existing) {
        throw new Error('A dashboard with this code already exists');
      }
    }

    // Cannot modify system dashboards
    if (dashboard.isSystem && dto.isSystem === false) {
      throw new Error('Cannot modify system dashboard flag');
    }

    Object.assign(dashboard, {
      ...dto,
      updatedById,
    });

    return this.dashboardRepository.save(dashboard);
  }

  async delete(id: string, tenantId: string, deletedById?: string): Promise<boolean> {
    const dashboard = await this.findOne(id, tenantId);
    if (!dashboard) return false;

    // Cannot delete system dashboards
    if (dashboard.isSystem) {
      throw new Error('Cannot delete system dashboards');
    }

    dashboard.deletedAt = new Date();
    dashboard.deletedById = deletedById || null;
    await this.dashboardRepository.save(dashboard);

    return true;
  }

  // ==================== Dashboard Operations ====================

  async getDefaultDashboard(
    tenantId: string,
    dashboardType?: DashboardType
  ): Promise<Dashboard | null> {
    const where: FindOptionsWhere<Dashboard> = {
      tenantId,
      isDefault: true,
      isActive: true,
      deletedAt: IsNull(),
    };

    if (dashboardType) {
      where.dashboardType = dashboardType;
    }

    return this.dashboardRepository.findOne({
      where,
      relations: ['widgets'],
    });
  }

  async setAsDefault(
    id: string,
    tenantId: string,
    updatedById?: string
  ): Promise<Dashboard | null> {
    const dashboard = await this.findOne(id, tenantId);
    if (!dashboard) return null;

    // Remove default from other dashboards of same type
    await this.dashboardRepository.update(
      { tenantId, dashboardType: dashboard.dashboardType, isDefault: true },
      { isDefault: false }
    );

    dashboard.isDefault = true;
    dashboard.updatedById = updatedById || null;

    return this.dashboardRepository.save(dashboard);
  }

  async updateViewCount(id: string, tenantId: string): Promise<void> {
    await this.dashboardRepository.update(
      { id, tenantId },
      {
        viewCount: () => 'view_count + 1',
        lastViewedAt: new Date(),
      }
    );
  }

  async clone(
    id: string,
    tenantId: string,
    newCode: string,
    newName: string,
    createdById?: string
  ): Promise<Dashboard> {
    const source = await this.findOne(id, tenantId);
    if (!source) {
      throw new Error('Source dashboard not found');
    }

    // Check new code doesn't exist
    const existingCode = await this.findByCode(newCode, tenantId);
    if (existingCode) {
      throw new Error('A dashboard with this code already exists');
    }

    // Create cloned dashboard
    const cloned = this.dashboardRepository.create({
      tenantId,
      code: newCode,
      name: newName,
      description: source.description,
      dashboardType: source.dashboardType,
      visibility: 'private' as DashboardVisibility,
      ownerId: createdById || null,
      fraccionamientoId: source.fraccionamientoId,
      layout: source.layout,
      theme: source.theme,
      refreshInterval: source.refreshInterval,
      defaultDateRange: source.defaultDateRange,
      defaultFilters: source.defaultFilters,
      allowedRoles: null,
      isDefault: false,
      isActive: true,
      isSystem: false,
      sortOrder: 0,
      createdById,
    });

    const savedDashboard = await this.dashboardRepository.save(cloned);

    // Clone widgets
    if (source.widgets && source.widgets.length > 0) {
      for (const widget of source.widgets) {
        await this.createWidget(tenantId, {
          dashboardId: savedDashboard.id,
          title: widget.title,
          subtitle: widget.subtitle,
          widgetType: widget.widgetType,
          dataSourceType: widget.dataSourceType,
          dataSource: widget.dataSource,
          config: widget.config,
          chartOptions: widget.chartOptions,
          thresholds: widget.thresholds,
          gridX: widget.gridX,
          gridY: widget.gridY,
          gridWidth: widget.gridWidth,
          gridHeight: widget.gridHeight,
          minWidth: widget.minWidth,
          minHeight: widget.minHeight,
          refreshInterval: widget.refreshInterval,
          cacheDuration: widget.cacheDuration,
          drillDownConfig: widget.drillDownConfig,
          clickAction: widget.clickAction,
          isActive: widget.isActive,
          sortOrder: widget.sortOrder,
        }, createdById);
      }
    }

    return this.findOne(savedDashboard.id, tenantId) as Promise<Dashboard>;
  }

  async getUserDashboards(
    tenantId: string,
    userId: string,
    roles?: string[]
  ): Promise<Dashboard[]> {
    const queryBuilder = this.dashboardRepository
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.widgets', 'w')
      .where('d.tenant_id = :tenantId', { tenantId })
      .andWhere('d.is_active = true')
      .andWhere('d.deleted_at IS NULL')
      .andWhere(
        '(d.visibility = :company OR d.owner_id = :userId OR (d.visibility IN (:...visibilities) AND (d.allowed_roles IS NULL OR d.allowed_roles && :roles)))',
        {
          company: 'company',
          userId,
          visibilities: ['team', 'department'],
          roles: roles || [],
        }
      )
      .orderBy('d.sort_order', 'ASC')
      .addOrderBy('d.name', 'ASC');

    return queryBuilder.getMany();
  }

  // ==================== Widget CRUD ====================

  async getWidgets(dashboardId: string, tenantId: string): Promise<DashboardWidget[]> {
    return this.widgetRepository.find({
      where: { dashboardId, tenantId, deletedAt: IsNull() },
      order: { sortOrder: 'ASC', gridY: 'ASC', gridX: 'ASC' },
    });
  }

  async getWidget(id: string, tenantId: string): Promise<DashboardWidget | null> {
    return this.widgetRepository.findOne({
      where: { id, tenantId, deletedAt: IsNull() },
    });
  }

  async createWidget(
    tenantId: string,
    dto: CreateWidgetDto,
    createdById?: string
  ): Promise<DashboardWidget> {
    const widget = this.widgetRepository.create({
      ...dto,
      tenantId,
      createdById,
    });

    return this.widgetRepository.save(widget);
  }

  async updateWidget(
    id: string,
    tenantId: string,
    dto: UpdateWidgetDto,
    updatedById?: string
  ): Promise<DashboardWidget | null> {
    const widget = await this.getWidget(id, tenantId);
    if (!widget) return null;

    Object.assign(widget, {
      ...dto,
      updatedById,
    });

    return this.widgetRepository.save(widget);
  }

  async deleteWidget(id: string, tenantId: string): Promise<boolean> {
    const widget = await this.getWidget(id, tenantId);
    if (!widget) return false;

    widget.deletedAt = new Date();
    await this.widgetRepository.save(widget);

    return true;
  }

  async updateWidgetPositions(
    tenantId: string,
    positions: Array<{ id: string; gridX: number; gridY: number; gridWidth: number; gridHeight: number }>
  ): Promise<void> {
    for (const pos of positions) {
      await this.widgetRepository.update(
        { id: pos.id, tenantId },
        {
          gridX: pos.gridX,
          gridY: pos.gridY,
          gridWidth: pos.gridWidth,
          gridHeight: pos.gridHeight,
        }
      );
    }
  }

  // ==================== Widget Query CRUD ====================

  async getWidgetQueries(widgetId: string): Promise<WidgetQuery[]> {
    return this.widgetQueryRepository.find({
      where: { widgetId },
      order: { name: 'ASC' },
    });
  }

  async getWidgetQuery(id: string): Promise<WidgetQuery | null> {
    return this.widgetQueryRepository.findOne({ where: { id } });
  }

  async createWidgetQuery(dto: CreateWidgetQueryDto): Promise<WidgetQuery> {
    const query = this.widgetQueryRepository.create(dto);
    return this.widgetQueryRepository.save(query);
  }

  async updateWidgetQuery(
    id: string,
    dto: UpdateWidgetQueryDto
  ): Promise<WidgetQuery | null> {
    const query = await this.getWidgetQuery(id);
    if (!query) return null;

    Object.assign(query, dto);
    return this.widgetQueryRepository.save(query);
  }

  async deleteWidgetQuery(id: string): Promise<boolean> {
    const result = await this.widgetQueryRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  async updateQueryCache(id: string): Promise<void> {
    await this.widgetQueryRepository.update(id, {
      lastCachedAt: new Date(),
    });
  }

  // ==================== Export Operations ====================

  async export(
    id: string,
    tenantId: string,
    format: 'pdf' | 'excel' | 'csv'
  ): Promise<{ filePath: string; mimeType: string }> {
    const dashboard = await this.findOne(id, tenantId);
    if (!dashboard) {
      throw new Error('Dashboard not found');
    }

    // This would integrate with actual export logic
    const mimeTypes: Record<string, string> = {
      pdf: 'application/pdf',
      excel: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      csv: 'text/csv',
    };

    return {
      filePath: `/exports/dashboard_${id}_${Date.now()}.${format}`,
      mimeType: mimeTypes[format],
    };
  }
}
