import TranslationDictionary from '../models/TranslationDictionary.js';
import TranslationMapping from '../models/TranslationMapping.js';
import Token from '../models/Token.js';
import sequelize from '../config/database.js';
import { Op } from 'sequelize';

class TranslationService {
  /**
   * Create a new translation dictionary
   */
  async createDictionary(data, userId) {
    const dictionary = await TranslationDictionary.create({
      name: data.name,
      description: data.description,
      projectId: data.projectId,
      createdBy: userId,
      status: data.status || 'draft',
      metadata: {
        sourceSystem: data.sourceSystem || null,
        targetSystem: data.targetSystem || null,
        coverage: 0,
        validationStatus: 'pending',
        lastValidated: null,
        tags: data.tags || []
      }
    });

    return dictionary;
  }

  /**
   * Update an existing dictionary
   */
  async updateDictionary(id, data, userId) {
    const dictionary = await TranslationDictionary.findByPk(id);

    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    // Update fields
    if (data.name) dictionary.name = data.name;
    if (data.description !== undefined) dictionary.description = data.description;
    if (data.status) dictionary.status = data.status;

    // Update metadata
    if (data.sourceSystem !== undefined || data.targetSystem !== undefined || data.tags) {
      dictionary.metadata = {
        ...dictionary.metadata,
        ...(data.sourceSystem !== undefined && { sourceSystem: data.sourceSystem }),
        ...(data.targetSystem !== undefined && { targetSystem: data.targetSystem }),
        ...(data.tags && { tags: data.tags })
      };
    }

    await dictionary.save();
    return dictionary;
  }

  /**
   * Get dictionary with mappings
   */
  async getDictionary(id) {
    const dictionary = await TranslationDictionary.findByPk(id);

    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    const mappings = await TranslationMapping.findAll({
      where: { dictionaryId: id },
      order: [['sourceToken', 'ASC']]
    });

    return {
      ...dictionary.toJSON(),
      mappings
    };
  }

  /**
   * List dictionaries with filters
   */
  async listDictionaries(filters = {}) {
    const where = {};

    if (filters.projectId) {
      where.projectId = filters.projectId;
    }

    if (filters.status) {
      where.status = filters.status;
    }

    if (filters.createdBy) {
      where.createdBy = filters.createdBy;
    }

    const dictionaries = await TranslationDictionary.findAll({
      where,
      order: [['createdAt', 'DESC']],
      limit: filters.limit || 50,
      offset: filters.offset || 0
    });

    const total = await TranslationDictionary.count({ where });

    return { dictionaries, total };
  }

  /**
   * Create a mapping
   */
  async createMapping(dictionaryId, mappingData, userId) {
    // Verify dictionary exists
    const dictionary = await TranslationDictionary.findByPk(dictionaryId);
    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    const mapping = await TranslationMapping.create({
      dictionaryId,
      sourceToken: mappingData.sourceToken,
      targetToken: mappingData.targetToken,
      transformRule: mappingData.transformRule || null,
      validated: mappingData.validated || false,
      confidence: mappingData.confidence || 1.0,
      notes: mappingData.notes || null
    });

    // Recalculate coverage
    await this._updateCoverage(dictionaryId);

    return mapping;
  }

  /**
   * Update a mapping
   */
  async updateMapping(dictionaryId, mappingId, data, userId) {
    const mapping = await TranslationMapping.findOne({
      where: { id: mappingId, dictionaryId }
    });

    if (!mapping) {
      throw new Error('Mapping not found');
    }

    if (data.sourceToken) mapping.sourceToken = data.sourceToken;
    if (data.targetToken) mapping.targetToken = data.targetToken;
    if (data.transformRule !== undefined) mapping.transformRule = data.transformRule;
    if (data.validated !== undefined) mapping.validated = data.validated;
    if (data.confidence !== undefined) mapping.confidence = data.confidence;
    if (data.notes !== undefined) mapping.notes = data.notes;

    await mapping.save();

    // Recalculate coverage
    await this._updateCoverage(dictionaryId);

    return mapping;
  }

  /**
   * Delete a mapping
   */
  async deleteMapping(dictionaryId, mappingId, userId) {
    const mapping = await TranslationMapping.findOne({
      where: { id: mappingId, dictionaryId }
    });

    if (!mapping) {
      throw new Error('Mapping not found');
    }

    await mapping.destroy();

    // Recalculate coverage
    await this._updateCoverage(dictionaryId);

    return { success: true };
  }

  /**
   * Bulk import mappings
   * Uses transaction to ensure atomicity
   */
  async bulkImportMappings(dictionaryId, mappings, userId) {
    // Verify dictionary exists
    const dictionary = await TranslationDictionary.findByPk(dictionaryId);
    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    const results = {
      created: 0,
      updated: 0,
      errors: []
    };

    // Use transaction for atomicity
    const transaction = await sequelize.transaction();

    try {
      for (const mapping of mappings) {
        try {
          // Check if mapping exists
          const existing = await TranslationMapping.findOne({
            where: {
              dictionaryId,
              sourceToken: mapping.sourceToken
            },
            transaction
          });

          if (existing) {
            // Update
            existing.targetToken = mapping.targetToken;
            existing.transformRule = mapping.transformRule || null;
            existing.confidence = mapping.confidence || 1.0;
            await existing.save({ transaction });
            results.updated++;
          } else {
            // Create
            await TranslationMapping.create({
              dictionaryId,
              sourceToken: mapping.sourceToken,
              targetToken: mapping.targetToken,
              transformRule: mapping.transformRule || null,
              validated: mapping.validated || false,
              confidence: mapping.confidence || 1.0,
              notes: mapping.notes || null
            }, { transaction });
            results.created++;
          }
        } catch (error) {
          results.errors.push({
            mapping,
            error: error.message
          });
          // Continue processing remaining mappings even if one fails
        }
      }

      // If all mappings failed, rollback
      if (results.errors.length === mappings.length && mappings.length > 0) {
        await transaction.rollback();
        throw new Error('All mappings failed to import');
      }

      // Recalculate coverage within transaction
      await this._updateCoverage(dictionaryId);

      // Commit transaction
      await transaction.commit();

      return results;
    } catch (error) {
      // Rollback on any error
      await transaction.rollback();
      throw error;
    }
  }

  /**
   * Validate dictionary mappings
   */
  async validateDictionary(dictionaryId) {
    const dictionary = await TranslationDictionary.findByPk(dictionaryId);
    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    const mappings = await TranslationMapping.findAll({
      where: { dictionaryId }
    });

    const validation = {
      total: mappings.length,
      validated: 0,
      invalid: [],
      warnings: []
    };

    for (const mapping of mappings) {
      // Check if source token exists in system
      const sourceExists = await Token.findOne({
        where: { name: mapping.sourceToken, projectId: dictionary.projectId }
      });

      if (!sourceExists) {
        validation.warnings.push({
          mappingId: mapping.id,
          sourceToken: mapping.sourceToken,
          issue: 'Source token not found in project'
        });
      }

      // Check if target token format is valid
      if (!mapping.targetToken || mapping.targetToken.trim() === '') {
        validation.invalid.push({
          mappingId: mapping.id,
          sourceToken: mapping.sourceToken,
          issue: 'Target token is empty'
        });
      } else {
        validation.validated++;
      }
    }

    // Update dictionary metadata
    dictionary.metadata = {
      ...dictionary.metadata,
      validationStatus: validation.invalid.length === 0 ? 'valid' : 'invalid',
      lastValidated: new Date().toISOString()
    };
    await dictionary.save();

    return validation;
  }

  /**
   * Calculate coverage percentage
   * @param {string} dictionaryId - Dictionary ID
   * @param {boolean} forceRefresh - Force refresh of cached token count
   */
  async calculateCoverage(dictionaryId, forceRefresh = false) {
    const dictionary = await TranslationDictionary.findByPk(dictionaryId);
    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    let totalTokens;

    // Use cached token count if available and not stale (unless force refresh)
    const cachedCount = dictionary.metadata?.cachedTokenCount;
    const cacheTimestamp = dictionary.metadata?.tokenCountCachedAt;
    const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
    const isCacheValid = cachedCount !== undefined &&
                         cacheTimestamp &&
                         (Date.now() - new Date(cacheTimestamp).getTime() < CACHE_TTL);

    if (!forceRefresh && isCacheValid) {
      totalTokens = cachedCount;
    } else {
      // Fetch fresh count and update cache
      totalTokens = await Token.count({
        where: { projectId: dictionary.projectId }
      });

      dictionary.metadata = {
        ...dictionary.metadata,
        cachedTokenCount: totalTokens,
        tokenCountCachedAt: new Date().toISOString()
      };
      await dictionary.save();
    }

    const mappedTokens = await TranslationMapping.count({
      where: { dictionaryId }
    });

    const coverage = totalTokens > 0 ? (mappedTokens / totalTokens) * 100 : 0;

    return {
      totalTokens,
      mappedTokens,
      unmappedTokens: totalTokens - mappedTokens,
      coveragePercentage: Math.round(coverage * 100) / 100
    };
  }

  /**
   * Internal: Update dictionary coverage metadata
   * Uses cached token count for performance
   */
  async _updateCoverage(dictionaryId) {
    const dictionary = await TranslationDictionary.findByPk(dictionaryId);
    if (!dictionary) return;

    // Use cached token count (don't force refresh on every mapping change)
    const coverage = await this.calculateCoverage(dictionaryId, false);

    dictionary.metadata = {
      ...dictionary.metadata,
      coverage: coverage.coveragePercentage
    };

    await dictionary.save();
  }

  /**
   * Export dictionary as JSON
   */
  async exportDictionary(dictionaryId) {
    const dictionary = await TranslationDictionary.findByPk(dictionaryId);
    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    const mappings = await TranslationMapping.findAll({
      where: { dictionaryId },
      order: [['sourceToken', 'ASC']]
    });

    return {
      name: dictionary.name,
      description: dictionary.description,
      version: dictionary.version,
      metadata: dictionary.metadata,
      mappings: mappings.map(m => ({
        sourceToken: m.sourceToken,
        targetToken: m.targetToken,
        transformRule: m.transformRule,
        validated: m.validated,
        confidence: m.confidence,
        notes: m.notes
      })),
      exportedAt: new Date().toISOString()
    };
  }

  /**
   * Delete dictionary (soft delete by archiving)
   */
  async deleteDictionary(id, userId) {
    const dictionary = await TranslationDictionary.findByPk(id);
    if (!dictionary) {
      throw new Error('Dictionary not found');
    }

    dictionary.status = 'archived';
    await dictionary.save();

    return { success: true };
  }
}

export default new TranslationService();
