/**
 * Data Model Service
 * Manages data model entities, fields, and relationships for report builder
 *
 * @module Reports
 */

import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { DataModelEntity } from '../entities/data-model-entity.entity';
import { DataModelField } from '../entities/data-model-field.entity';
import { DataModelRelationship, RelationshipType } from '../entities/data-model-relationship.entity';

export interface DataModelEntitySearchParams {
  search?: string;
  schemaName?: string;
  isActive?: boolean;
  isMultiTenant?: boolean;
  limit?: number;
  offset?: number;
}

export interface CreateDataModelEntityDto {
  name: string;
  displayName: string;
  description?: string | null;
  schemaName: string;
  tableName: string;
  primaryKeyColumn?: string;
  tenantColumn?: string | null;
  isMultiTenant?: boolean;
  isActive?: boolean;
}

export interface UpdateDataModelEntityDto extends Partial<CreateDataModelEntityDto> {}

export interface CreateDataModelFieldDto {
  entityId: string;
  name: string;
  displayName: string;
  description?: string | null;
  dataType: string;
  isNullable?: boolean;
  isFilterable?: boolean;
  isSortable?: boolean;
  isGroupable?: boolean;
  isAggregatable?: boolean;
  aggregationFunctions?: string[];
  formatPattern?: string | null;
  displayFormat?: string | null;
  isActive?: boolean;
  sortOrder?: number;
}

export interface UpdateDataModelFieldDto extends Partial<Omit<CreateDataModelFieldDto, 'entityId'>> {}

export interface CreateDataModelRelationshipDto {
  sourceEntityId: string;
  targetEntityId: string;
  name: string;
  relationshipType: RelationshipType;
  sourceColumn: string;
  targetColumn: string;
  joinCondition?: string | null;
  isActive?: boolean;
}

export interface UpdateDataModelRelationshipDto extends Partial<Omit<CreateDataModelRelationshipDto, 'sourceEntityId' | 'targetEntityId'>> {}

export class DataModelService {
  constructor(
    private readonly entityRepository: Repository<DataModelEntity>,
    private readonly fieldRepository: Repository<DataModelField>,
    private readonly relationshipRepository: Repository<DataModelRelationship>
  ) {}

  // ==================== Entity CRUD ====================

  async findAllEntities(params: DataModelEntitySearchParams): Promise<{ data: DataModelEntity[]; total: number }> {
    const {
      search,
      schemaName,
      isActive,
      isMultiTenant,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<DataModelEntity>[] = [];
    const baseWhere: FindOptionsWhere<DataModelEntity> = {};

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

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

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

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

    const [data, total] = await this.entityRepository.findAndCount({
      where,
      relations: ['fields'],
      take: limit,
      skip: offset,
      order: { schemaName: 'ASC', displayName: 'ASC' },
    });

    return { data, total };
  }

  async findOneEntity(id: string): Promise<DataModelEntity | null> {
    return this.entityRepository.findOne({
      where: { id },
      relations: ['fields', 'sourceRelationships', 'targetRelationships'],
    });
  }

  async findEntityByName(name: string): Promise<DataModelEntity | null> {
    return this.entityRepository.findOne({
      where: { name },
      relations: ['fields'],
    });
  }

  async createEntity(dto: CreateDataModelEntityDto): Promise<DataModelEntity> {
    // Check for existing name
    const existingName = await this.findEntityByName(dto.name);
    if (existingName) {
      throw new Error('An entity with this name already exists');
    }

    const entity = this.entityRepository.create(dto);
    return this.entityRepository.save(entity);
  }

  async updateEntity(id: string, dto: UpdateDataModelEntityDto): Promise<DataModelEntity | null> {
    const entity = await this.findOneEntity(id);
    if (!entity) return null;

    // If changing name, check for duplicates
    if (dto.name && dto.name !== entity.name) {
      const existing = await this.findEntityByName(dto.name);
      if (existing) {
        throw new Error('An entity with this name already exists');
      }
    }

    Object.assign(entity, dto);
    return this.entityRepository.save(entity);
  }

  async deleteEntity(id: string): Promise<boolean> {
    const entity = await this.findOneEntity(id);
    if (!entity) return false;

    // This will cascade delete fields and relationships
    await this.entityRepository.delete(id);
    return true;
  }

  async getEntitiesBySchema(schemaName: string): Promise<DataModelEntity[]> {
    return this.entityRepository.find({
      where: { schemaName, isActive: true },
      relations: ['fields'],
      order: { displayName: 'ASC' },
    });
  }

  async getActiveEntities(): Promise<DataModelEntity[]> {
    return this.entityRepository.find({
      where: { isActive: true },
      relations: ['fields'],
      order: { schemaName: 'ASC', displayName: 'ASC' },
    });
  }

  // ==================== Field CRUD ====================

  async getFields(entityId: string): Promise<DataModelField[]> {
    return this.fieldRepository.find({
      where: { entityId },
      order: { sortOrder: 'ASC', displayName: 'ASC' },
    });
  }

  async getField(id: string): Promise<DataModelField | null> {
    return this.fieldRepository.findOne({
      where: { id },
      relations: ['entity'],
    });
  }

  async getFieldByName(entityId: string, name: string): Promise<DataModelField | null> {
    return this.fieldRepository.findOne({
      where: { entityId, name },
    });
  }

  async createField(dto: CreateDataModelFieldDto): Promise<DataModelField> {
    // Check for existing field with same name in entity
    const existingField = await this.getFieldByName(dto.entityId, dto.name);
    if (existingField) {
      throw new Error('A field with this name already exists in this entity');
    }

    const field = this.fieldRepository.create(dto);
    return this.fieldRepository.save(field);
  }

  async createFieldsBatch(entityId: string, fields: Omit<CreateDataModelFieldDto, 'entityId'>[]): Promise<DataModelField[]> {
    const fieldEntities = fields.map((f, index) =>
      this.fieldRepository.create({
        ...f,
        entityId,
        sortOrder: f.sortOrder ?? index,
      })
    );

    return this.fieldRepository.save(fieldEntities);
  }

  async updateField(id: string, dto: UpdateDataModelFieldDto): Promise<DataModelField | null> {
    const field = await this.getField(id);
    if (!field) return null;

    // If changing name, check for duplicates
    if (dto.name && dto.name !== field.name) {
      const existing = await this.getFieldByName(field.entityId, dto.name);
      if (existing) {
        throw new Error('A field with this name already exists in this entity');
      }
    }

    Object.assign(field, dto);
    return this.fieldRepository.save(field);
  }

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

  async getFilterableFields(entityId: string): Promise<DataModelField[]> {
    return this.fieldRepository.find({
      where: { entityId, isFilterable: true, isActive: true },
      order: { sortOrder: 'ASC' },
    });
  }

  async getSortableFields(entityId: string): Promise<DataModelField[]> {
    return this.fieldRepository.find({
      where: { entityId, isSortable: true, isActive: true },
      order: { sortOrder: 'ASC' },
    });
  }

  async getGroupableFields(entityId: string): Promise<DataModelField[]> {
    return this.fieldRepository.find({
      where: { entityId, isGroupable: true, isActive: true },
      order: { sortOrder: 'ASC' },
    });
  }

  async getAggregatableFields(entityId: string): Promise<DataModelField[]> {
    return this.fieldRepository.find({
      where: { entityId, isAggregatable: true, isActive: true },
      order: { sortOrder: 'ASC' },
    });
  }

  // ==================== Relationship CRUD ====================

  async getRelationships(entityId: string): Promise<{
    source: DataModelRelationship[];
    target: DataModelRelationship[];
  }> {
    const [source, target] = await Promise.all([
      this.relationshipRepository.find({
        where: { sourceEntityId: entityId },
        relations: ['sourceEntity', 'targetEntity'],
      }),
      this.relationshipRepository.find({
        where: { targetEntityId: entityId },
        relations: ['sourceEntity', 'targetEntity'],
      }),
    ]);

    return { source, target };
  }

  async getRelationship(id: string): Promise<DataModelRelationship | null> {
    return this.relationshipRepository.findOne({
      where: { id },
      relations: ['sourceEntity', 'targetEntity'],
    });
  }

  async createRelationship(dto: CreateDataModelRelationshipDto): Promise<DataModelRelationship> {
    // Check for existing relationship
    const existing = await this.relationshipRepository.findOne({
      where: {
        sourceEntityId: dto.sourceEntityId,
        targetEntityId: dto.targetEntityId,
        name: dto.name,
      },
    });

    if (existing) {
      throw new Error('A relationship with this name already exists between these entities');
    }

    const relationship = this.relationshipRepository.create(dto);
    return this.relationshipRepository.save(relationship);
  }

  async updateRelationship(
    id: string,
    dto: UpdateDataModelRelationshipDto
  ): Promise<DataModelRelationship | null> {
    const relationship = await this.getRelationship(id);
    if (!relationship) return null;

    Object.assign(relationship, dto);
    return this.relationshipRepository.save(relationship);
  }

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

  async getRelatedEntities(entityId: string): Promise<DataModelEntity[]> {
    const relationships = await this.getRelationships(entityId);

    const relatedIds = new Set<string>([
      ...relationships.source.map((r) => r.targetEntityId),
      ...relationships.target.map((r) => r.sourceEntityId),
    ]);

    if (relatedIds.size === 0) return [];

    return this.entityRepository.findByIds([...relatedIds]);
  }

  // ==================== Data Model Operations ====================

  async getCompleteModel(): Promise<{
    entities: DataModelEntity[];
    relationships: DataModelRelationship[];
  }> {
    const [entities, relationships] = await Promise.all([
      this.entityRepository.find({
        where: { isActive: true },
        relations: ['fields'],
        order: { schemaName: 'ASC', displayName: 'ASC' },
      }),
      this.relationshipRepository.find({
        where: { isActive: true },
        relations: ['sourceEntity', 'targetEntity'],
      }),
    ]);

    return { entities, relationships };
  }

  async syncFromDatabase(schemaName: string): Promise<{
    created: number;
    updated: number;
  }> {
    // This would introspect the database schema and create/update
    // DataModelEntity and DataModelField records
    // For now, this is a placeholder
    return { created: 0, updated: 0 };
  }

  async validateQuery(
    entityIds: string[],
    fields: string[],
    filters?: Record<string, any>[],
    grouping?: string[],
    sorting?: Record<string, any>[]
  ): Promise<{
    valid: boolean;
    errors: string[];
  }> {
    const errors: string[] = [];

    // Validate entities exist
    for (const entityId of entityIds) {
      const entity = await this.findOneEntity(entityId);
      if (!entity) {
        errors.push(`Entity ${entityId} not found`);
      }
    }

    // Additional validation would check:
    // - Fields belong to selected entities
    // - Relationships exist between entities
    // - Filters are valid for field types
    // - Grouping fields are groupable
    // - Sorting fields are sortable

    return {
      valid: errors.length === 0,
      errors,
    };
  }

  async buildQuery(
    entityIds: string[],
    fields: Array<{ entityId: string; fieldName: string; alias?: string }>,
    joins?: Array<{ relationshipId: string; joinType: 'inner' | 'left' }>,
    filters?: Record<string, any>[],
    grouping?: string[],
    sorting?: Array<{ field: string; direction: 'ASC' | 'DESC' }>
  ): Promise<string> {
    // This would build a SQL query based on the data model
    // For now, this is a placeholder
    return '';
  }

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

  async export(
    format: 'pdf' | 'excel' | 'csv'
  ): Promise<{ filePath: string; mimeType: string }> {
    // This would export the data model documentation
    const mimeTypes: Record<string, string> = {
      pdf: 'application/pdf',
      excel: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      csv: 'text/csv',
    };

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

  async generate(
    entityIds: string[],
    params?: Record<string, any>
  ): Promise<{ data: any[]; rowCount: number }> {
    // This would generate a report based on the data model
    // For now, this is a placeholder
    return {
      data: [],
      rowCount: 0,
    };
  }

  async schedule(
    entityIds: string[],
    cronExpression: string
  ): Promise<void> {
    // This would schedule a data model-based report
    // For now, this is a placeholder
  }

  async getHistory(
    limit = 20
  ): Promise<DataModelEntity[]> {
    return this.entityRepository.find({
      order: { updatedAt: 'DESC' },
      take: limit,
    });
  }
}
