import pool from '../config/database';
import logger from '../utils/logger';

// Default configs — used when no DB config exists yet
const DEFAULT_GROUP_HIERARCHY: Record<string, string[]> = {
  'dashboard_mitglied': ['dashboard_chargen', 'dashboard_atemschutz', 'dashboard_moderator', 'dashboard_zeugmeister', 'dashboard_fahrmeister', 'dashboard_kommando'],
  'dashboard_chargen': ['dashboard_kommando'],
  'dashboard_atemschutz': ['dashboard_kommando'],
  'dashboard_moderator': ['dashboard_kommando'],
  'dashboard_zeugmeister': ['dashboard_kommando'],
  'dashboard_fahrmeister': ['dashboard_kommando'],
  'dashboard_kommando': [],
};

const DEFAULT_PERMISSION_DEPS: Record<string, string[]> = {
  'kalender:create': ['kalender:view'],
  'kalender:widget_events': ['kalender:view'],
  'kalender:widget_quick_add': ['kalender:view', 'kalender:create'],
  'fahrzeugbuchungen:create': ['fahrzeugbuchungen:view'],
  'fahrzeugbuchungen:manage': ['fahrzeugbuchungen:view', 'fahrzeugbuchungen:create'],
  'fahrzeugbuchungen:widget': ['fahrzeugbuchungen:view'],
  'fahrzeuge:create': ['fahrzeuge:view'],
  'fahrzeuge:change_status': ['fahrzeuge:view'],
  'fahrzeuge:manage_maintenance': ['fahrzeuge:view'],
  'fahrzeuge:delete': ['fahrzeuge:view', 'fahrzeuge:create'],
  'fahrzeuge:widget': ['fahrzeuge:view'],
  'einsaetze:view_reports': ['einsaetze:view'],
  'einsaetze:create': ['einsaetze:view'],
  'einsaetze:delete': ['einsaetze:view', 'einsaetze:create'],
  'einsaetze:manage_personnel': ['einsaetze:view'],
  'ausruestung:create': ['ausruestung:view'],
  'ausruestung:manage_maintenance': ['ausruestung:view'],
  'ausruestung:delete': ['ausruestung:view', 'ausruestung:create'],
  'ausruestung:widget': ['ausruestung:view'],
  'mitglieder:view_all': ['mitglieder:view_own'],
  'mitglieder:edit': ['mitglieder:view_own', 'mitglieder:view_all'],
  'mitglieder:create_profile': ['mitglieder:view_own', 'mitglieder:view_all', 'mitglieder:edit'],
  'atemschutz:create': ['atemschutz:view'],
  'atemschutz:delete': ['atemschutz:view', 'atemschutz:create'],
  'atemschutz:widget': ['atemschutz:view'],
  'wissen:widget_recent': ['wissen:view'],
  'wissen:widget_search': ['wissen:view'],
  'admin:write': ['admin:view'],
  'buchhaltung:create':          ['buchhaltung:view'],
  'buchhaltung:edit':            ['buchhaltung:view'],
  'buchhaltung:delete':          ['buchhaltung:view', 'buchhaltung:create'],
  'buchhaltung:manage_accounts': ['buchhaltung:view'],
  'buchhaltung:manage_settings': ['buchhaltung:view'],
  'buchhaltung:export':          ['buchhaltung:view'],
  'buchhaltung:widget':          ['buchhaltung:view'],
};

export interface FeatureGroupRow {
  id: string;
  label: string;
  sort_order: number;
  maintenance: boolean;
}

export interface PermissionRow {
  id: string;
  feature_group_id: string;
  label: string;
  description: string | null;
  sort_order: number;
}

export interface MatrixData {
  featureGroups: FeatureGroupRow[];
  permissions: PermissionRow[];
  groups: string[];
  grants: Record<string, string[]>;
  maintenance: Record<string, boolean>;
}

class PermissionService {
  private groupPermissions: Map<string, Set<string>> = new Map();
  private maintenanceFlags: Map<string, boolean> = new Map();

  async loadCache(): Promise<void> {
    try {
      // Load group permissions
      const gpResult = await pool.query('SELECT authentik_group, permission_id FROM group_permissions');
      const newMap = new Map<string, Set<string>>();
      for (const row of gpResult.rows) {
        if (!newMap.has(row.authentik_group)) {
          newMap.set(row.authentik_group, new Set());
        }
        newMap.get(row.authentik_group)!.add(row.permission_id);
      }

      // dashboard_admin always holds every permission in the system
      const allPermsResult = await pool.query('SELECT id FROM permissions');
      newMap.set('dashboard_admin', new Set(allPermsResult.rows.map((r: any) => r.id)));

      this.groupPermissions = newMap;

      // Load maintenance flags
      const mResult = await pool.query('SELECT id, maintenance FROM feature_groups');
      const newFlags = new Map<string, boolean>();
      for (const row of mResult.rows) {
        newFlags.set(row.id, row.maintenance);
      }
      this.maintenanceFlags = newFlags;

      logger.info('Permission cache loaded', {
        groups: this.groupPermissions.size,
        featureGroups: this.maintenanceFlags.size,
      });
    } catch (error) {
      logger.error('Failed to load permission cache', { error });
      // Don't throw — service can still function with empty cache
      // dashboard_admin bypass ensures admins always have access
    }
  }

  getEffectivePermissions(groups: string[]): string[] {
    const permSet = new Set<string>();
    for (const group of groups) {
      const perms = this.groupPermissions.get(group);
      if (perms) {
        for (const p of perms) {
          permSet.add(p);
        }
      }
    }
    return Array.from(permSet);
  }

  hasPermission(groups: string[], permission: string): boolean {
    for (const group of groups) {
      const perms = this.groupPermissions.get(group);
      if (perms?.has(permission)) return true;
    }
    return false;
  }

  isFeatureInMaintenance(featureGroup: string): boolean {
    return this.maintenanceFlags.get(featureGroup) ?? false;
  }

  getMaintenanceFlags(): Record<string, boolean> {
    const result: Record<string, boolean> = {};
    for (const [k, v] of this.maintenanceFlags) {
      result[k] = v;
    }
    return result;
  }

  // ── Admin methods ──

  async getMatrix(): Promise<MatrixData> {
    const [fgResult, pResult, gpResult, userGroupsResult] = await Promise.all([
      pool.query('SELECT id, label, sort_order, maintenance FROM feature_groups ORDER BY sort_order'),
      pool.query('SELECT id, feature_group_id, label, description, sort_order FROM permissions ORDER BY feature_group_id, sort_order'),
      pool.query('SELECT authentik_group, permission_id FROM group_permissions'),
      // Also include all dashboard_ groups from users table
      pool.query(`SELECT DISTINCT g AS group_name FROM users, unnest(authentik_groups) AS g WHERE g LIKE 'dashboard_%' AND g != 'dashboard_admin'`),
    ]);

    const grants: Record<string, string[]> = {};
    const groupSet = new Set<string>();

    // Add groups from group_permissions
    for (const row of gpResult.rows) {
      groupSet.add(row.authentik_group);
      if (!grants[row.authentik_group]) grants[row.authentik_group] = [];
      grants[row.authentik_group].push(row.permission_id);
    }

    // Also add groups from users table (they may have no permissions yet)
    for (const row of userGroupsResult.rows) {
      groupSet.add(row.group_name);
      if (!grants[row.group_name]) grants[row.group_name] = [];
    }

    const maintenance: Record<string, boolean> = {};
    for (const row of fgResult.rows) {
      maintenance[row.id] = row.maintenance;
    }

    return {
      featureGroups: fgResult.rows,
      permissions: pResult.rows,
      groups: Array.from(groupSet).sort(),
      grants,
      maintenance,
    };
  }

  async getKnownGroups(): Promise<string[]> {
    const result = await pool.query(
      'SELECT DISTINCT authentik_group FROM group_permissions ORDER BY authentik_group'
    );
    return result.rows.map((r: any) => r.authentik_group);
  }

  async getUnknownGroups(): Promise<string[]> {
    // Groups from users table that have zero permissions assigned
    // (they appear in the matrix but admin should be notified)
    const result = await pool.query(`
      SELECT DISTINCT g AS group_name
      FROM users, unnest(authentik_groups) AS g
      WHERE g LIKE 'dashboard_%'
        AND g != 'dashboard_admin'
        AND g NOT IN (SELECT DISTINCT authentik_group FROM group_permissions)
      ORDER BY group_name
    `);
    return result.rows.map((r: any) => r.group_name);
  }

  async setGroupPermissions(group: string, permIds: string[], grantedBy: string): Promise<void> {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');

      // Validate permission IDs exist (filter out stale/invalid ones)
      let validPermIds = permIds;
      if (permIds.length > 0) {
        const validResult = await client.query(
          'SELECT id FROM permissions WHERE id = ANY($1)',
          [permIds]
        );
        const validSet = new Set(validResult.rows.map((r: any) => r.id));
        validPermIds = permIds.filter(p => validSet.has(p));
      }

      // Remove all existing permissions for this group
      await client.query('DELETE FROM group_permissions WHERE authentik_group = $1', [group]);

      // Insert new permissions
      if (validPermIds.length > 0) {
        const values = validPermIds.map((_p, i) =>
          `($1, $${i + 2}, $${validPermIds.length + 2})`
        ).join(', ');
        await client.query(
          `INSERT INTO group_permissions (authentik_group, permission_id, granted_by)
           VALUES ${values}
           ON CONFLICT DO NOTHING`,
          [group, ...validPermIds, grantedBy]
        );
      }

      await client.query('COMMIT');

      // Reload cache
      await this.loadCache();

      logger.info('Group permissions updated', { group, permissionCount: validPermIds.length, grantedBy });
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async deleteGroup(group: string): Promise<void> {
    await pool.query('DELETE FROM group_permissions WHERE authentik_group = $1', [group]);
    await this.loadCache();
    logger.info('Group deleted from permissions', { group });
  }

  /**
   * Bulk-update permissions for multiple groups in a single transaction.
   * Reloads cache once at the end.
   */
  async setMultipleGroupPermissions(
    updates: { group: string; permissions: string[] }[],
    grantedBy: string,
  ): Promise<{ droppedPermissions: string[] }> {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');

      // Collect all referenced permission IDs to validate in one query
      const allPermIds = new Set<string>();
      for (const u of updates) {
        for (const p of u.permissions) allPermIds.add(p);
      }

      let validSet = new Set<string>();
      if (allPermIds.size > 0) {
        const validResult = await client.query(
          'SELECT id FROM permissions WHERE id = ANY($1)',
          [Array.from(allPermIds)]
        );
        validSet = new Set(validResult.rows.map((r: any) => r.id));
      }

      const allDropped: string[] = [];

      for (const { group, permissions } of updates) {
        const validPermIds = permissions.filter(p => validSet.has(p));
        const droppedPermIds = permissions.filter(p => !validSet.has(p));
        if (droppedPermIds.length > 0) {
          logger.warn('Permissions dropped during save — not found in permissions table', {
            group,
            droppedPermIds,
          });
          allDropped.push(...droppedPermIds);
        }

        await client.query('DELETE FROM group_permissions WHERE authentik_group = $1', [group]);

        if (validPermIds.length > 0) {
          const values = validPermIds.map((_p, i) =>
            `($1, $${i + 2}, $${validPermIds.length + 2})`
          ).join(', ');
          await client.query(
            `INSERT INTO group_permissions (authentik_group, permission_id, granted_by)
             VALUES ${values}
             ON CONFLICT DO NOTHING`,
            [group, ...validPermIds, grantedBy]
          );
        }
      }

      await client.query('COMMIT');
      await this.loadCache();

      logger.info('Bulk group permissions updated', {
        groupCount: updates.length,
        grantedBy,
      });

      return { droppedPermissions: [...new Set(allDropped)] };
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async setMaintenanceFlag(featureGroup: string, active: boolean): Promise<void> {
    await pool.query(
      'UPDATE feature_groups SET maintenance = $1 WHERE id = $2',
      [active, featureGroup]
    );
    await this.loadCache();
    logger.info('Maintenance flag updated', { featureGroup, active });
  }

  // ── Dependency config (stored in app_settings) ──

  async getGroupHierarchy(): Promise<Record<string, string[]>> {
    try {
      const result = await pool.query(
        "SELECT value FROM app_settings WHERE key = 'permission_group_hierarchy'"
      );
      if (result.rows.length > 0 && result.rows[0].value) {
        const val = typeof result.rows[0].value === 'string'
          ? JSON.parse(result.rows[0].value)
          : result.rows[0].value;
        return val;
      }
    } catch (error) {
      logger.warn('Failed to load group hierarchy from DB, using defaults', { error });
    }
    return DEFAULT_GROUP_HIERARCHY;
  }

  async setGroupHierarchy(hierarchy: Record<string, string[]>, userId: string): Promise<void> {
    await pool.query(
      `INSERT INTO app_settings (key, value, updated_by, updated_at)
       VALUES ('permission_group_hierarchy', $1, $2, NOW())
       ON CONFLICT (key) DO UPDATE SET value = $1, updated_by = $2, updated_at = NOW()`,
      [JSON.stringify(hierarchy), userId]
    );
    logger.info('Group hierarchy updated', { userId });
  }

  async getPermissionDeps(): Promise<Record<string, string[]>> {
    try {
      const result = await pool.query(
        "SELECT value FROM app_settings WHERE key = 'permission_deps'"
      );
      if (result.rows.length > 0 && result.rows[0].value) {
        const val = typeof result.rows[0].value === 'string'
          ? JSON.parse(result.rows[0].value)
          : result.rows[0].value;
        return val;
      }
    } catch (error) {
      logger.warn('Failed to load permission deps from DB, using defaults', { error });
    }
    return DEFAULT_PERMISSION_DEPS;
  }

  async setPermissionDeps(deps: Record<string, string[]>, userId: string): Promise<void> {
    await pool.query(
      `INSERT INTO app_settings (key, value, updated_by, updated_at)
       VALUES ('permission_deps', $1, $2, NOW())
       ON CONFLICT (key) DO UPDATE SET value = $1, updated_by = $2, updated_at = NOW()`,
      [JSON.stringify(deps), userId]
    );
    logger.info('Permission deps updated', { userId });
  }

  async getDependencyConfig(): Promise<{
    groupHierarchy: Record<string, string[]>;
    permissionDeps: Record<string, string[]>;
  }> {
    const [groupHierarchy, permissionDeps] = await Promise.all([
      this.getGroupHierarchy(),
      this.getPermissionDeps(),
    ]);
    return { groupHierarchy, permissionDeps };
  }

  /**
   * Returns users whose Authentik groups grant a specific permission,
   * or who are dashboard_admin (always have all permissions).
   */
  async getUsersWithPermission(permissionId: string): Promise<Array<{ id: string; name: string }>> {
    // Find all groups that have this permission
    const groupsWithPerm: string[] = [];
    for (const [group, perms] of this.groupPermissions.entries()) {
      if (perms.has(permissionId)) {
        groupsWithPerm.push(group);
      }
    }
    // Always include dashboard_admin
    groupsWithPerm.push('dashboard_admin');

    const result = await pool.query(
      `SELECT DISTINCT u.id, COALESCE(u.name, u.email) AS name
       FROM users u
       WHERE u.authentik_groups && $1::text[]
       ORDER BY name ASC`,
      [groupsWithPerm]
    );
    return result.rows;
  }
}

export const permissionService = new PermissionService();
