const db = require('../../database/database');
const { mergeLanguageDescriptors } = require('../../utils/languageUtils');

class I18nPreferencesRepository {
  _safeJsonArray(value) {
    if (Array.isArray(value)) return value;
    if (value == null) return [];

    try {
      const parsed = typeof value === 'string' ? JSON.parse(value) : value;
      return Array.isArray(parsed) ? parsed : [];
    } catch (_) {
      return [];
    }
  }

  _normalizeRow(row) {
    const categories = this._safeJsonArray(row?.categories_json);
    const globalKeys = this._safeJsonArray(row?.global_keys_json);
    return {
      categories,
      globalKeys,
    };
  }

  _safeBoolean(value, fallback) {
    if (value === undefined || value === null) return fallback;
    if (typeof value === 'boolean') return value;
    const normalized = String(value).trim().toLowerCase();
    if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
    if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
    return fallback;
  }

  async get() {
    const [rows] = await db.query('SELECT * FROM i18n_preferences WHERE id = 1 LIMIT 1');
    if (!rows.length) {
      return { categories: [], globalKeys: [] };
    }
    return this._normalizeRow(rows[0]);
  }

  async listLanguages({ enabledOnly = true } = {}) {
    try {
      const [rows] = await db.query(
        `SELECT language_code AS languageCode,
                label,
                is_enabled AS isEnabled,
                is_custom AS isCustom
         FROM i18n_languages
         ${enabledOnly ? 'WHERE is_enabled = 1' : ''}
         ORDER BY language_code`
      );

      const languages = mergeLanguageDescriptors(rows || []);
      return enabledOnly ? languages.filter((entry) => entry.isEnabled !== false) : languages;
    } catch (_) {
      return mergeLanguageDescriptors([]);
    }
  }

  async upsert({ categories, globalKeys, updatedByUserId } = {}) {
    const current = await this.get();

    const nextCategories = categories !== undefined ? categories : current.categories;
    const nextGlobalKeys = globalKeys !== undefined ? globalKeys : current.globalKeys;

    await db.query(
      `INSERT INTO i18n_preferences (id, categories_json, global_keys_json, updated_by_user_id)
       VALUES (1, ?, ?, ?)
       ON DUPLICATE KEY UPDATE
         categories_json = VALUES(categories_json),
         global_keys_json = VALUES(global_keys_json),
         updated_by_user_id = VALUES(updated_by_user_id)`,
      [JSON.stringify(nextCategories || []), JSON.stringify(nextGlobalKeys || []), updatedByUserId || null]
    );

    return this.get();
  }

  async clear(updatedByUserId) {
    await db.query(
      `INSERT INTO i18n_preferences (id, categories_json, global_keys_json, updated_by_user_id)
       VALUES (1, ?, ?, ?)
       ON DUPLICATE KEY UPDATE
         categories_json = VALUES(categories_json),
         global_keys_json = VALUES(global_keys_json),
         updated_by_user_id = VALUES(updated_by_user_id)`,
      [JSON.stringify([]), JSON.stringify([]), updatedByUserId || null]
    );

    return this.get();
  }

  async _tableExists(conn, tableName) {
    const [rows] = await conn.query(
      `SELECT 1
       FROM INFORMATION_SCHEMA.TABLES
       WHERE TABLE_SCHEMA = DATABASE()
         AND TABLE_NAME = ?
       LIMIT 1`,
      [tableName]
    );
    return rows.length > 0;
  }

  async _columnExists(conn, tableName, columnName) {
    const [rows] = await conn.query(
      `SELECT 1
       FROM INFORMATION_SCHEMA.COLUMNS
       WHERE TABLE_SCHEMA = DATABASE()
         AND TABLE_NAME = ?
         AND COLUMN_NAME = ?
       LIMIT 1`,
      [tableName, columnName]
    );
    return rows.length > 0;
  }

  async deleteLanguageEntries(languageCode, updatedByUserId) {
    const conn = await db.getConnection();
    const safeLanguageCode = String(languageCode || '').trim();

    const targets = [
      // Language metadata
      { table: 'i18n_languages', possibleColumns: ['language_code', 'lang', 'code'] },
      { table: 'i18n_language_metadata', possibleColumns: ['language_code', 'lang', 'code'] },

      // Translation/custom-value stores
      { table: 'i18n_translation_overrides', possibleColumns: ['language_code', 'lang'] },
      { table: 'i18n_translations', possibleColumns: ['language_code', 'lang'] },

      // Potential language-scoped preference/link tables
      { table: 'i18n_preferences_languages', possibleColumns: ['language_code', 'lang'] },
      { table: 'i18n_preference_categories', possibleColumns: ['language_code', 'lang'] },
      { table: 'i18n_preference_global_keys', possibleColumns: ['language_code', 'lang'] },
    ];

    let deletedRows = 0;
    const touchedTables = [];

    try {
      await conn.beginTransaction();

      for (const target of targets) {
        const exists = await this._tableExists(conn, target.table);
        if (!exists) continue;

        let deleteColumn = null;
        for (const col of target.possibleColumns) {
          if (await this._columnExists(conn, target.table, col)) {
            deleteColumn = col;
            break;
          }
        }
        if (!deleteColumn) continue;

        const [result] = await conn.query(
          `DELETE FROM \`${target.table}\` WHERE \`${deleteColumn}\` = ?`,
          [safeLanguageCode]
        );
        const affected = Number(result?.affectedRows || 0);
        if (affected > 0) {
          deletedRows += affected;
          touchedTables.push(target.table);
        }
      }

      if (await this._tableExists(conn, 'i18n_preferences')) {
        await conn.query(
          `UPDATE i18n_preferences
           SET updated_by_user_id = ?,
               updated_at = CURRENT_TIMESTAMP
           WHERE id = 1`,
          [updatedByUserId || null]
        );
      }

      await conn.commit();
      return { deletedRows, touchedTables };
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
  }

  async upsertLanguage(conn, language, updatedByUserId) {
    if (!language || !language.languageCode) return null;

    const languageCode = String(language.languageCode).trim().toLowerCase();
    const label = String(language.label || languageCode).trim();
    const isEnabled = this._safeBoolean(language.isEnabled, true);
    const isCustom = this._safeBoolean(language.isCustom, true);

    await conn.query(
      `INSERT INTO i18n_languages (language_code, label, is_enabled, is_custom, created_by_user_id, updated_by_user_id)
       VALUES (?, ?, ?, ?, ?, ?)
       ON DUPLICATE KEY UPDATE
         label = VALUES(label),
         is_enabled = VALUES(is_enabled),
         is_custom = VALUES(is_custom),
         updated_by_user_id = VALUES(updated_by_user_id)`,
      [languageCode, label, isEnabled ? 1 : 0, isCustom ? 1 : 0, updatedByUserId || null, updatedByUserId || null]
    );

    const [rows] = await conn.query(
      `SELECT language_code AS languageCode,
              label,
              is_enabled AS isEnabled,
              is_custom AS isCustom,
              created_at AS createdAt,
              updated_at AS updatedAt
       FROM i18n_languages
       WHERE language_code = ?
       LIMIT 1`,
      [languageCode]
    );
    return rows[0] || null;
  }

  async upsertTranslationOverrides(conn, translationEntries, updatedByUserId) {
    if (!Array.isArray(translationEntries) || !translationEntries.length) {
      return { upsertedCount: 0 };
    }

    let upsertedCount = 0;
    for (const entry of translationEntries) {
      const languageCode = String(entry.languageCode || '').trim().toLowerCase();
      const namespace = String(entry.namespace || '').trim();
      const key = String(entry.key || '').trim();
      const value = entry.value == null ? '' : String(entry.value);
      const isCustom = this._safeBoolean(entry.isCustom, true);

      if (!languageCode || !namespace || !key) continue;

      await conn.query(
        `INSERT INTO i18n_translation_overrides
           (language_code, namespace, t_key, t_value, is_custom, created_by_user_id, updated_by_user_id)
         VALUES (?, ?, ?, ?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
           t_value = VALUES(t_value),
           is_custom = VALUES(is_custom),
           updated_by_user_id = VALUES(updated_by_user_id)`,
        [
          languageCode,
          namespace,
          key,
          value,
          isCustom ? 1 : 0,
          updatedByUserId || null,
          updatedByUserId || null,
        ]
      );
      upsertedCount += 1;
    }

    return { upsertedCount };
  }

  async upsertBundle({ categories, globalKeys, language, translations, updatedByUserId } = {}) {
    const conn = await db.getConnection();
    try {
      await conn.beginTransaction();

      const [prefRows] = await conn.query('SELECT * FROM i18n_preferences WHERE id = 1 LIMIT 1');
      const current = prefRows.length
        ? this._normalizeRow(prefRows[0])
        : { categories: [], globalKeys: [] };

      const nextCategories = categories !== undefined ? categories : current.categories;
      const nextGlobalKeys = globalKeys !== undefined ? globalKeys : current.globalKeys;

      await conn.query(
        `INSERT INTO i18n_preferences (id, categories_json, global_keys_json, updated_by_user_id)
         VALUES (1, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
           categories_json = VALUES(categories_json),
           global_keys_json = VALUES(global_keys_json),
           updated_by_user_id = VALUES(updated_by_user_id)`,
        [JSON.stringify(nextCategories || []), JSON.stringify(nextGlobalKeys || []), updatedByUserId || null]
      );

      const languagesToEnsure = new Map();
      if (language && language.languageCode) {
        languagesToEnsure.set(String(language.languageCode).toLowerCase(), language);
      }
      for (const t of (Array.isArray(translations) ? translations : [])) {
        const code = String(t.languageCode || '').trim().toLowerCase();
        if (!code) continue;
        if (!languagesToEnsure.has(code)) {
          languagesToEnsure.set(code, {
            languageCode: code,
            label: code.toUpperCase(),
            isEnabled: true,
            isCustom: true,
          });
        }
      }

      let upsertedLanguage = null;
      for (const [, langPayload] of languagesToEnsure) {
        const row = await this.upsertLanguage(conn, langPayload, updatedByUserId);
        if (language && row && row.languageCode === String(language.languageCode).toLowerCase()) {
          upsertedLanguage = row;
        }
      }

      const translationResult = await this.upsertTranslationOverrides(conn, translations, updatedByUserId);

      const [savedRows] = await conn.query('SELECT * FROM i18n_preferences WHERE id = 1 LIMIT 1');
      const preferences = savedRows.length
        ? this._normalizeRow(savedRows[0])
        : { categories: [], globalKeys: [] };

      await conn.commit();
      return {
        preferences,
        language: upsertedLanguage,
        translationsUpserted: translationResult.upsertedCount,
      };
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
  }

  async listTranslations({ languageCode, namespace } = {}) {
    const filters = [];
    const params = [];

    if (languageCode) {
      filters.push('language_code = ?');
      params.push(String(languageCode).trim().toLowerCase());
    }
    if (namespace) {
      filters.push('namespace = ?');
      params.push(String(namespace).trim());
    }

    const whereClause = filters.length ? `WHERE ${filters.join(' AND ')}` : '';
    const [rows] = await db.query(
      `SELECT language_code AS languageCode,
              namespace,
              t_key AS \`key\`,
              t_value AS value,
              is_custom AS isCustom,
              updated_at AS updatedAt
       FROM i18n_translation_overrides
       ${whereClause}
       ORDER BY language_code, namespace, t_key`,
      params
    );

    return rows || [];
  }

  async upsertTranslations({ translations, updatedByUserId } = {}) {
    const conn = await db.getConnection();
    try {
      await conn.beginTransaction();

      // Make sure every translation language exists in metadata table.
      const ensured = new Set();
      for (const t of (translations || [])) {
        const code = String(t.languageCode || '').trim().toLowerCase();
        if (!code || ensured.has(code)) continue;

        await this.upsertLanguage(
          conn,
          {
            languageCode: code,
            label: code.toUpperCase(),
            isEnabled: true,
            isCustom: true,
          },
          updatedByUserId
        );
        ensured.add(code);
      }

      const result = await this.upsertTranslationOverrides(conn, translations || [], updatedByUserId);
      await conn.commit();
      return result;
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
  }

  async getScanSummary({ languageCode } = {}) {
    const code = languageCode ? String(languageCode).trim().toLowerCase() : null;

    const [languages] = await db.query(
      `SELECT language_code AS languageCode,
              label,
              is_enabled AS isEnabled,
              is_custom AS isCustom
       FROM i18n_languages
       ORDER BY language_code`
    );

    const [namespaces] = await db.query(
      `SELECT DISTINCT namespace
       FROM i18n_translation_overrides
       ${code ? 'WHERE language_code = ?' : ''}
       ORDER BY namespace`,
      code ? [code] : []
    );

    const [countsByLanguage] = await db.query(
      `SELECT language_code AS languageCode, COUNT(*) AS entryCount
       FROM i18n_translation_overrides
       ${code ? 'WHERE language_code = ?' : ''}
       GROUP BY language_code
       ORDER BY language_code`,
      code ? [code] : []
    );

    const prefs = await this.get();
    const categoryNamespaces = Array.isArray(prefs.categories)
      ? [...new Set(prefs.categories.flatMap((c) => (Array.isArray(c?.namespaces) ? c.namespaces : [])))]
      : [];

    return {
      languages: languages || [],
      namespaces: (namespaces || []).map((r) => r.namespace),
      countsByLanguage: countsByLanguage || [],
      categories: prefs.categories || [],
      globalKeys: prefs.globalKeys || [],
      categoryNamespaces,
    };
  }
}

module.exports = I18nPreferencesRepository;
