const GenericDBApi = require('./base.api');
const db = require('../models');
const Utils = require('../utils');

const Sequelize = db.Sequelize;
const Op = Sequelize.Op;

class Project_element_defaultsDBApi extends GenericDBApi {
  static get MODEL() {
    return db.project_element_defaults;
  }

  static get TABLE_NAME() {
    return 'project_element_defaults';
  }

  static get SEARCHABLE_FIELDS() {
    return ['name', 'element_type'];
  }

  static get RANGE_FIELDS() {
    return ['sort_order', 'snapshot_version'];
  }

  static get ENUM_FIELDS() {
    return [];
  }

  static get ASSOCIATIONS() {
    return [{ field: 'project', setter: 'setProject', isArray: false }];
  }

  static get RELATION_FILTERS() {
    return [
      {
        filterKey: 'project',
        model: db.projects,
        as: 'project',
        searchField: 'name',
      },
    ];
  }

  static get FIND_ALL_INCLUDES() {
    return [{ association: 'project' }, { association: 'source_element' }];
  }

  static get CSV_FIELDS() {
    return [
      'id',
      'element_type',
      'name',
      'sort_order',
      'projectId',
      'snapshot_version',
      'createdAt',
    ];
  }

  static get AUTOCOMPLETE_FIELD() {
    return 'name';
  }

  // Declarative field configuration using base class patterns
  static get JSON_FIELDS() {
    return ['settings_json'];
  }

  static get FIELD_DEFAULTS() {
    return {
      element_type: { default: null },
      name: { default: null },
      sort_order: { default: 0 },
      source_element_id: { default: null },
      snapshot_version: { default: 1 },
    };
  }

  static getFieldMapping(data) {
    // Apply base class transformations (JSON fields, defaults, transformers)
    const mapped = super.getFieldMapping(data);

    // Custom mapping for projectId field (accepts both projectId and project)
    if (mapped.project && !mapped.projectId) {
      mapped.projectId = mapped.project;
    }

    return {
      id: mapped.id || undefined,
      element_type: mapped.element_type,
      name: mapped.name,
      sort_order: mapped.sort_order,
      settings_json: mapped.settings_json,
      source_element_id: mapped.source_element_id,
      snapshot_version: mapped.snapshot_version,
      projectId: mapped.projectId,
    };
  }

  /**
   * Custom findAll with project filtering
   * Supports both 'project' and 'projectId' query params for consistency
   */
  static async findAll(filter = {}, options = {}) {
    filter = filter || {};
    const limit = filter.limit || 0;
    const currentPage = +filter.page || 0;
    const offset = currentPage * limit;

    let where = {};

    // Support both 'project' and 'projectId' query params
    const projectFilter = filter.project || filter.projectId;
    const terms = projectFilter ? projectFilter.split('|') : [];
    const validUuids = Utils.filterValidUuids(terms);

    let include = [
      {
        model: db.projects,
        as: 'project',
        where: projectFilter
          ? {
              [Op.or]: [
                ...(validUuids.length > 0
                  ? [{ id: { [Op.in]: validUuids } }]
                  : []),
                {
                  name: {
                    [Op.or]: terms.map((term) => ({ [Op.iLike]: `%${term}%` })),
                  },
                },
              ],
            }
          : {},
      },
      {
        model: db.element_type_defaults,
        as: 'source_element',
        required: false,
      },
    ];

    if (filter.id) {
      if (!Utils.isValidUuid(filter.id)) {
        return { rows: [], count: 0 };
      }
      where.id = filter.id;
    }

    for (const field of this.SEARCHABLE_FIELDS) {
      if (filter[field]) {
        where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]);
      }
    }

    for (const field of this.RANGE_FIELDS) {
      const rangeKey = `${field}Range`;
      if (filter[rangeKey]) {
        const [start, end] = filter[rangeKey];
        if (start !== undefined && start !== null && start !== '') {
          where[field] = { ...where[field], [Op.gte]: start };
        }
        if (end !== undefined && end !== null && end !== '') {
          where[field] = { ...where[field], [Op.lte]: end };
        }
      }
    }

    for (const field of this.ENUM_FIELDS) {
      if (filter[field] !== undefined) {
        where[field] = filter[field];
      }
    }

    if (filter.createdAtRange) {
      const [start, end] = filter.createdAtRange;
      if (start !== undefined && start !== null && start !== '') {
        where.createdAt = { ...where.createdAt, [Op.gte]: start };
      }
      if (end !== undefined && end !== null && end !== '') {
        where.createdAt = { ...where.createdAt, [Op.lte]: end };
      }
    }

    const queryOptions = {
      where,
      include,
      distinct: true,
      order:
        filter.field && filter.sort
          ? [[filter.field, filter.sort]]
          : [['sort_order', 'asc']],
      transaction: options.transaction,
    };

    if (!options.countOnly) {
      queryOptions.limit = limit ? Number(limit) : undefined;
      queryOptions.offset = offset ? Number(offset) : undefined;
    }

    try {
      const { rows, count } = await this.MODEL.findAndCountAll(queryOptions);
      return {
        rows: options.countOnly ? [] : rows,
        count,
      };
    } catch (error) {
      console.error('Error executing query:', error);
      throw error;
    }
  }

  /**
   * Find project element default by element type for a specific project
   */
  static async findByElementType(projectId, elementType, options = {}) {
    return this.MODEL.findOne({
      where: {
        projectId,
        element_type: elementType,
        deletedAt: null,
      },
      ...options,
    });
  }

  /**
   * Snapshot all global element defaults to a project
   * Used when creating a new project
   */
  static async snapshotGlobalDefaults(projectId, options = {}) {
    const Element_type_defaultsDBApi = require('./element_type_defaults');

    // Get all global defaults
    const globalDefaults = await Element_type_defaultsDBApi.findAll({});

    if (!globalDefaults?.rows?.length) {
      return [];
    }

    // Dedupe by element_type (keep first occurrence)
    // Prevents unique constraint violations if global defaults have duplicates
    const seenTypes = new Set();
    const dedupedDefaults = globalDefaults.rows.filter((row) => {
      if (seenTypes.has(row.element_type)) {
        console.warn(
          `Duplicate element_type in global defaults: ${row.element_type} (skipping)`,
        );
        return false;
      }
      seenTypes.add(row.element_type);
      return true;
    });

    const now = new Date();
    const currentUserId = options.currentUser?.id || null;

    // Create project defaults from global defaults
    const projectDefaults = await this.MODEL.bulkCreate(
      dedupedDefaults.map((globalDefault) => ({
        projectId,
        element_type: globalDefault.element_type,
        name: globalDefault.name,
        sort_order: globalDefault.sort_order,
        settings_json: globalDefault.default_settings_json,
        source_element_id: globalDefault.id,
        snapshot_version: 1,
        createdById: currentUserId,
        updatedById: currentUserId,
        createdAt: now,
        updatedAt: now,
      })),
      {
        transaction: options.transaction,
        returning: true,
      },
    );

    return projectDefaults;
  }

  /**
   * Reset a project element default to the current global default
   */
  static async resetToGlobal(id, options = {}) {
    const Element_type_defaultsDBApi = require('./element_type_defaults');

    // Ensure global defaults are initialized
    await Element_type_defaultsDBApi.ensureInitialized();

    // Find the project default
    const projectDefault = await this.MODEL.findByPk(id);
    if (!projectDefault) {
      throw new Error('Project element default not found');
    }

    // Find the matching global default
    const globalDefault = await Element_type_defaultsDBApi.MODEL.findOne({
      where: {
        element_type: projectDefault.element_type,
        deletedAt: null,
      },
    });

    if (!globalDefault) {
      throw new Error(
        `No global default found for element type: ${projectDefault.element_type}`,
      );
    }

    // Update with global settings and increment version
    const now = new Date();
    await projectDefault.update(
      {
        name: globalDefault.name,
        sort_order: globalDefault.sort_order,
        settings_json: globalDefault.default_settings_json,
        source_element_id: globalDefault.id,
        snapshot_version: projectDefault.snapshot_version + 1,
        updatedById: options.currentUser?.id || null,
        updatedAt: now,
      },
      {
        transaction: options.transaction,
      },
    );

    return projectDefault.reload();
  }

  /**
   * Get diff between project default and current global default
   */
  static async getDiffFromGlobal(id) {
    const Element_type_defaultsDBApi = require('./element_type_defaults');

    // Ensure global defaults are initialized
    await Element_type_defaultsDBApi.ensureInitialized();

    // Find the project default
    const projectDefault = await this.MODEL.findByPk(id);
    if (!projectDefault) {
      throw new Error('Project element default not found');
    }

    // Find the matching global default
    const globalDefault = await Element_type_defaultsDBApi.MODEL.findOne({
      where: {
        element_type: projectDefault.element_type,
        deletedAt: null,
      },
    });

    if (!globalDefault) {
      return {
        projectDefault,
        globalDefault: null,
        hasGlobalDefault: false,
        isDifferent: true,
      };
    }

    // Parse JSON settings for comparison
    const projectSettings =
      typeof projectDefault.settings_json === 'string'
        ? JSON.parse(projectDefault.settings_json || '{}')
        : projectDefault.settings_json || {};

    const globalSettings =
      typeof globalDefault.default_settings_json === 'string'
        ? JSON.parse(globalDefault.default_settings_json || '{}')
        : globalDefault.default_settings_json || {};

    const isDifferent =
      JSON.stringify(projectSettings) !== JSON.stringify(globalSettings) ||
      projectDefault.name !== globalDefault.name ||
      projectDefault.sort_order !== globalDefault.sort_order;

    return {
      projectDefault,
      globalDefault,
      hasGlobalDefault: true,
      isDifferent,
      projectSettings,
      globalSettings,
    };
  }
}

module.exports = Project_element_defaultsDBApi;
