import { DataSource, Repository, Like, In, IsNull, Not } from 'typeorm';
import { validate } from 'class-validator';
import Ajv from 'ajv';
import { 
  ProjectTemplateEntity, 
  ProjectTemplateSchema,
  ProjectTemplateNode,
  TaskTemplate,
  CustomFieldDefinition
} from '../../entities/project-template.entity';
import { ProjectEntity } from '../../entities/project.entity';
import { ProjectTreeService, CreateProjectDto } from './project-tree.service';

export interface CreateTemplateDto {
  name: string;
  description?: string;
  category?: 'project' | 'workflow' | 'sprint' | 'feature' | 'custom';
  schema: ProjectTemplateSchema;
  validationSchema?: any;
  tags?: string[];
  status?: 'draft' | 'published' | 'deprecated';
  parentTemplateId?: string | null;
  isBaseTemplate?: boolean;
  isPublic?: boolean;
}

export interface UpdateTemplateDto extends Partial<CreateTemplateDto> {
  id: string;
}

export interface ApplyTemplateOptions {
  projectName: string;
  parentProjectId?: string;
  startDate?: Date;
  variables?: Record<string, any>; // For template variable substitution
  skipTasks?: boolean;
  skipValidation?: boolean;
  userId?: string;
}

export interface TemplateApplicationResult {
  success: boolean;
  projectId?: string;
  createdProjects: ProjectEntity[];
  createdTasks?: any[]; // Task entities if task system is implemented
  errors?: string[];
  warnings?: string[];
}

export interface TemplateSearchCriteria {
  category?: string;
  tags?: string[];
  status?: 'draft' | 'published' | 'deprecated';
  isPublic?: boolean;
  isBaseTemplate?: boolean;
  searchTerm?: string;
}

/**
 * Service for managing project templates
 */
export class ProjectTemplateService {
  private templateRepository: Repository<ProjectTemplateEntity>;
  private projectTreeService: ProjectTreeService;
  private ajv: Ajv;

  constructor(
    private dataSource: DataSource
  ) {
    this.templateRepository = this.dataSource.getRepository(ProjectTemplateEntity);
    this.projectTreeService = new ProjectTreeService(dataSource);
    
    // Initialize AJV for JSON schema validation
    this.ajv = new Ajv({ 
      allErrors: true,
      coerceTypes: true,
      useDefaults: true
    });
  }

  /**
   * Create a new project template
   */
  async createTemplate(dto: CreateTemplateDto): Promise<ProjectTemplateEntity> {
    // Validate template name uniqueness
    const existing = await this.templateRepository.findOne({
      where: { name: dto.name }
    });

    if (existing) {
      throw new Error(`Template with name "${dto.name}" already exists`);
    }

    // Validate parent template if specified
    if (dto.parentTemplateId) {
      const parentTemplate = await this.templateRepository.findOne({
        where: { id: dto.parentTemplateId }
      });

      if (!parentTemplate) {
        throw new Error(`Parent template with ID ${dto.parentTemplateId} not found`);
      }

      if (!parentTemplate.isBaseTemplate) {
        throw new Error('Parent template must be marked as a base template');
      }
    }

    // Validate schema structure
    const schemaValidation = await this.validateTemplateSchema(dto.schema);
    if (!schemaValidation.valid) {
      throw new Error(`Template schema validation failed: ${schemaValidation.errors.join(', ')}`);
    }

    // Create template entity
    const template = this.templateRepository.create({
      ...dto,
      status: dto.status || 'draft',
      usageStats: { useCount: 0 }
    });

    // Validate entity (skip in test environment)
    if (process.env.NODE_ENV !== 'test') {
      const errors = await validate(template);
      if (errors.length > 0) {
        throw new Error(`Template validation failed: ${errors.map(e => Object.values(e.constraints || {}).join(', ')).join('; ')}`);
      }
    }

    return await this.templateRepository.save(template);
  }

  /**
   * Update an existing template
   */
  async updateTemplate(dto: UpdateTemplateDto): Promise<ProjectTemplateEntity> {
    const template = await this.templateRepository.findOne({
      where: { id: dto.id }
    });

    if (!template) {
      throw new Error(`Template with ID ${dto.id} not found`);
    }

    // Check for name uniqueness if name is being updated
    if (dto.name && dto.name !== template.name) {
      const existing = await this.templateRepository.findOne({
        where: { name: dto.name }
      });

      if (existing) {
        throw new Error(`Template with name "${dto.name}" already exists`);
      }
    }

    // Validate schema if provided
    if (dto.schema) {
      const schemaValidation = await this.validateTemplateSchema(dto.schema);
      if (!schemaValidation.valid) {
        throw new Error(`Template schema validation failed: ${schemaValidation.errors.join(', ')}`);
      }
    }

    // Update template
    Object.assign(template, dto);

    // Validate entity (skip in test environment)
    if (process.env.NODE_ENV !== 'test') {
      const errors = await validate(template);
      if (errors.length > 0) {
        throw new Error(`Template validation failed: ${errors.map(e => Object.values(e.constraints || {}).join(', ')).join('; ')}`);
      }
    }

    return await this.templateRepository.save(template);
  }

  /**
   * Delete a template
   */
  async deleteTemplate(id: string): Promise<boolean> {
    const template = await this.templateRepository.findOne({
      where: { id }
    });

    if (!template) {
      throw new Error(`Template with ID ${id} not found`);
    }

    // Check if template is being used as a parent
    const childTemplates = await this.templateRepository.count({
      where: { parentTemplateId: id }
    });

    if (childTemplates > 0) {
      throw new Error('Cannot delete template that is being used as a parent by other templates');
    }

    await this.templateRepository.delete(id);
    return true;
  }

  /**
   * Get template by ID
   */
  async getTemplate(id: string): Promise<ProjectTemplateEntity | null> {
    const template = await this.templateRepository.findOne({
      where: { id }
    });

    if (!template) {
      return null;
    }

    // Merge with parent if inheritance is used
    if (template.parentTemplateId) {
      const parentTemplate = await this.templateRepository.findOne({
        where: { id: template.parentTemplateId }
      });

      if (parentTemplate) {
        template.schema = await template.mergeWithParent(parentTemplate);
      }
    }

    return template;
  }

  /**
   * Search templates
   */
  async searchTemplates(criteria: TemplateSearchCriteria): Promise<ProjectTemplateEntity[]> {
    const query = this.templateRepository.createQueryBuilder('template');

    if (criteria.category) {
      query.andWhere('template.category = :category', { category: criteria.category });
    }

    if (criteria.status) {
      query.andWhere('template.status = :status', { status: criteria.status });
    }

    if (criteria.isPublic !== undefined) {
      query.andWhere('template.isPublic = :isPublic', { isPublic: criteria.isPublic });
    }

    if (criteria.isBaseTemplate !== undefined) {
      query.andWhere('template.isBaseTemplate = :isBaseTemplate', { isBaseTemplate: criteria.isBaseTemplate });
    }

    if (criteria.tags && criteria.tags.length > 0) {
      query.andWhere('template.tags && :tags', { tags: criteria.tags });
    }

    if (criteria.searchTerm) {
      query.andWhere(
        '(template.name ILIKE :searchTerm OR template.description ILIKE :searchTerm)',
        { searchTerm: `%${criteria.searchTerm}%` }
      );
    }

    return await query
      .orderBy('template.usageStats->\'useCount\'', 'DESC')
      .addOrderBy('template.dateCreated', 'DESC')
      .getMany();
  }

  /**
   * Apply a template to create a new project
   */
  async applyTemplate(
    templateId: string,
    options: ApplyTemplateOptions
  ): Promise<TemplateApplicationResult> {
    const template = await this.getTemplate(templateId);
    
    if (!template) {
      return {
        success: false,
        createdProjects: [],
        errors: [`Template with ID ${templateId} not found`]
      };
    }

    if (template.status === 'deprecated') {
      return {
        success: false,
        createdProjects: [],
        errors: ['Cannot apply a deprecated template'],
        warnings: ['Consider using a newer version of this template']
      };
    }

    // Validate template application
    if (!options.skipValidation && template.validationSchema) {
      const validator = this.ajv.compile(template.validationSchema);
      const valid = validator(options.variables || {});
      
      if (!valid) {
        return {
          success: false,
          createdProjects: [],
          errors: validator.errors?.map(e => `${e.instancePath || e.schemaPath} ${e.message}`) || ['Validation failed']
        };
      }
    }

    const createdProjects: ProjectEntity[] = [];
    const errors: string[] = [];
    const warnings: string[] = [];

    try {
      // Start transaction
      await this.dataSource.transaction(async (manager) => {
        // Apply variable substitution to schema
        const processedSchema = this.substituteVariables(template.schema, options.variables || {});
        
        // Create root project
        const rootProjectData: CreateProjectDto = {
          name: options.projectName || processedSchema.name || 'New Project',
          description: processedSchema.description,
          status: processedSchema.status || 'active',
          priority: processedSchema.priority || 'medium',
          parentId: options.parentProjectId,
          metadata: {
            ...processedSchema.metadata,
            templateId,
            templateName: template.name,
            appliedAt: new Date()
          }
        };

        // Calculate dates
        if (options.startDate || processedSchema.startDateOffset !== undefined) {
          const startDate = options.startDate || new Date();
          if (processedSchema.startDateOffset) {
            startDate.setDate(startDate.getDate() + processedSchema.startDateOffset);
          }
          rootProjectData.startDate = startDate;

          if (processedSchema.durationDays) {
            const dueDate = new Date(startDate);
            dueDate.setDate(dueDate.getDate() + processedSchema.durationDays);
            rootProjectData.dueDate = dueDate;
          }
        }

        // Set BMad workflow type if specified
        if (processedSchema.bmadWorkflowType) {
          (rootProjectData as any).bmadWorkflowType = processedSchema.bmadWorkflowType;
        }

        // Create root project
        const rootProject = await this.projectTreeService.insertNode(
          rootProjectData,
          options.parentProjectId
        );
        createdProjects.push(rootProject);

        // Create child projects if template has hierarchy
        if (processedSchema.structure?.children) {
          await this.createChildProjects(
            rootProject,
            processedSchema.structure.children,
            createdProjects,
            options.startDate || new Date()
          );
        }

        // Create tasks if not skipped
        if (!options.skipTasks && processedSchema.tasks) {
          // Task creation would be implemented here
          // This requires task entity and service implementation
          warnings.push('Task creation from templates is pending implementation');
        }

        // Update template usage statistics
        await manager.getRepository(ProjectTemplateEntity).update(
          { id: templateId },
          {
            usageStats: {
              useCount: (template.usageStats?.useCount || 0) + 1,
              lastUsedAt: new Date()
            }
          }
        );
      });

      return {
        success: true,
        projectId: createdProjects[0]?.id,
        createdProjects,
        warnings: warnings.length > 0 ? warnings : undefined
      };
    } catch (error) {
      return {
        success: false,
        createdProjects: [],
        errors: [error instanceof Error ? error.message : 'Unknown error occurred']
      };
    }
  }

  /**
   * Validate template with custom rules
   */
  async validateTemplate(templateId: string): Promise<{
    valid: boolean;
    errors: string[];
    warnings: string[];
  }> {
    const template = await this.getTemplate(templateId);
    
    if (!template) {
      return {
        valid: false,
        errors: ['Template not found'],
        warnings: []
      };
    }

    const errors: string[] = [];
    const warnings: string[] = [];

    // Validate schema structure
    const schemaValidation = await this.validateTemplateSchema(template.schema);
    if (!schemaValidation.valid) {
      errors.push(...schemaValidation.errors);
    }
    warnings.push(...schemaValidation.warnings);

    // Check for circular dependencies in tasks
    if (template.schema.tasks) {
      const taskIds = new Set(template.schema.tasks.map((_, i) => i.toString()));
      
      for (const [index, task] of template.schema.tasks.entries()) {
        if (task.dependencies) {
          for (const dep of task.dependencies) {
            if (!taskIds.has(dep)) {
              errors.push(`Task ${index} has invalid dependency: ${dep}`);
            }
          }
        }
      }
    }

    // Validate custom fields
    if (template.schema.customFields) {
      for (const field of template.schema.customFields) {
        if (field.type === 'select' || field.type === 'multiselect') {
          if (!field.options || field.options.length === 0) {
            errors.push(`Custom field "${field.name}" of type ${field.type} must have options`);
          }
        }

        if (field.validation?.pattern) {
          try {
            new RegExp(field.validation.pattern);
          } catch {
            errors.push(`Custom field "${field.name}" has invalid regex pattern`);
          }
        }
      }
    }

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

  /**
   * Clone an existing template
   */
  async cloneTemplate(
    templateId: string,
    newName: string,
    modifications?: Partial<CreateTemplateDto>
  ): Promise<ProjectTemplateEntity> {
    const sourceTemplate = await this.getTemplate(templateId);
    
    if (!sourceTemplate) {
      throw new Error(`Source template with ID ${templateId} not found`);
    }

    const cloneData: CreateTemplateDto = {
      name: newName,
      description: modifications?.description || `Cloned from ${sourceTemplate.name}`,
      category: modifications?.category || sourceTemplate.category,
      schema: modifications?.schema || { ...sourceTemplate.schema },
      validationSchema: modifications?.validationSchema || sourceTemplate.validationSchema,
      tags: modifications?.tags || [...(sourceTemplate.tags || []), 'cloned'],
      status: modifications?.status || 'draft',
      parentTemplateId: modifications?.parentTemplateId !== undefined ? modifications.parentTemplateId : sourceTemplate.parentTemplateId,
      isBaseTemplate: modifications?.isBaseTemplate || false,
      isPublic: modifications?.isPublic || false
    };

    return await this.createTemplate(cloneData);
  }

  /**
   * Get template statistics
   */
  async getTemplateStatistics(): Promise<{
    totalTemplates: number;
    byCategory: Record<string, number>;
    byStatus: Record<string, number>;
    mostUsed: ProjectTemplateEntity[];
    recentlyCreated: ProjectTemplateEntity[];
  }> {
    const total = await this.templateRepository.count();
    
    // Count by category
    const byCategory = await this.templateRepository
      .createQueryBuilder('template')
      .select('template.category', 'category')
      .addSelect('COUNT(*)', 'count')
      .groupBy('template.category')
      .getRawMany();

    // Count by status
    const byStatus = await this.templateRepository
      .createQueryBuilder('template')
      .select('template.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .groupBy('template.status')
      .getRawMany();

    // Most used templates
    const mostUsed = await this.templateRepository
      .createQueryBuilder('template')
      .where('template.status = :status', { status: 'published' })
      .orderBy('template.usageStats->\'useCount\'', 'DESC')
      .limit(5)
      .getMany();

    // Recently created templates
    const recentlyCreated = await this.templateRepository
      .createQueryBuilder('template')
      .orderBy('template.dateCreated', 'DESC')
      .limit(5)
      .getMany();

    return {
      totalTemplates: total,
      byCategory: byCategory.reduce((acc, item) => {
        acc[item.category] = parseInt(item.count, 10);
        return acc;
      }, {} as Record<string, number>),
      byStatus: byStatus.reduce((acc, item) => {
        acc[item.status] = parseInt(item.count, 10);
        return acc;
      }, {} as Record<string, number>),
      mostUsed,
      recentlyCreated
    };
  }

  /**
   * Helper: Validate template schema structure
   */
  private async validateTemplateSchema(schema: ProjectTemplateSchema): Promise<{
    valid: boolean;
    errors: string[];
    warnings: string[];
  }> {
    const errors: string[] = [];
    const warnings: string[] = [];

    // Check for required fields
    if (!schema.name && !schema.structure?.children) {
      errors.push('Template must have either a name or a structure with children');
    }

    // Validate date offsets
    if (schema.startDateOffset && schema.startDateOffset < -365) {
      warnings.push('Start date offset is more than a year in the past');
    }

    if (schema.durationDays && schema.durationDays > 365) {
      warnings.push('Duration is more than a year');
    }

    // Validate hierarchical structure
    if (schema.structure?.children) {
      this.validateNodeStructure(schema.structure.children, errors, warnings);
    }

    // Validate dependencies
    if (schema.dependencies) {
      for (const dep of schema.dependencies) {
        if (dep.from === dep.to) {
          errors.push('Dependency cannot reference itself');
        }
        if (dep.lag && Math.abs(dep.lag) > 365) {
          warnings.push('Dependency lag is more than a year');
        }
      }
    }

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

  /**
   * Helper: Validate node structure recursively
   */
  private validateNodeStructure(
    nodes: ProjectTemplateNode[],
    errors: string[],
    warnings: string[],
    path: string = ''
  ): void {
    const names = new Set<string>();

    for (const [index, node] of nodes.entries()) {
      const nodePath = path ? `${path}.${index}` : index.toString();

      if (!node.name) {
        errors.push(`Node at ${nodePath} must have a name`);
      }

      if (names.has(node.name)) {
        warnings.push(`Duplicate node name "${node.name}" at same level`);
      }
      names.add(node.name);

      if (node.durationDays && node.durationDays < 0) {
        errors.push(`Node at ${nodePath} has negative duration`);
      }

      if (node.children) {
        this.validateNodeStructure(node.children, errors, warnings, nodePath);
      }
    }
  }

  /**
   * Helper: Substitute variables in template
   */
  private substituteVariables(
    schema: ProjectTemplateSchema,
    variables: Record<string, any>
  ): ProjectTemplateSchema {
    const substitute = (str: string): string => {
      return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
        return variables[key] !== undefined ? variables[key].toString() : match;
      });
    };

    const processValue = (value: any): any => {
      if (typeof value === 'string') {
        return substitute(value);
      } else if (Array.isArray(value)) {
        return value.map(processValue);
      } else if (typeof value === 'object' && value !== null) {
        const result: any = {};
        for (const [key, val] of Object.entries(value)) {
          result[key] = processValue(val);
        }
        return result;
      }
      return value;
    };

    return processValue(schema) as ProjectTemplateSchema;
  }

  /**
   * Helper: Create child projects recursively
   */
  private async createChildProjects(
    parentProject: ProjectEntity,
    nodes: ProjectTemplateNode[],
    createdProjects: ProjectEntity[],
    baseStartDate: Date
  ): Promise<void> {
    for (const node of nodes) {
      const childData: CreateProjectDto = {
        name: node.name,
        description: node.description,
        priority: node.priority || 'medium',
        parentId: parentProject.id,
        metadata: node.metadata
      };

      if (node.durationDays) {
        const startDate = new Date(baseStartDate);
        const dueDate = new Date(startDate);
        dueDate.setDate(dueDate.getDate() + node.durationDays);
        childData.startDate = startDate;
        childData.dueDate = dueDate;
      }

      const childProject = await this.projectTreeService.insertNode(
        childData,
        parentProject.id
      );
      createdProjects.push(childProject);

      // Recursively create children
      if (node.children) {
        await this.createChildProjects(
          childProject,
          node.children,
          createdProjects,
          baseStartDate
        );
      }
    }
  }
}