import {
  Guild,
  PermissionFlagsBits,
  PermissionsBitField,
  GuildBasedChannel,
  Role,
} from 'discord.js';
import { prisma } from '../database';
import { logger } from '../utils/logger';

// ─── Types ────────────────────────────────────────────────────────────────────

export type AuditStatus = 'SUCCESS' | 'WARNING' | 'FAIL';

export interface AuditResult {
  featureKey: string;
  status: AuditStatus;
  missingPermissions: string[];
  scope: 'guild' | 'channel' | 'hierarchy';
  channelId?: string;
  /** 역할 계층 관련 추가 정보 */
  hierarchyInfo?: {
    botRolePosition: number;
    targetRoleId: string;
    targetRolePosition: number;
    targetRoleName: string;
  };
}

// ─── Feature Definitions (Plugin-style) ───────────────────────────────────────
// 새 기능이 추가될 때 이 배열에 항목을 추가하기만 하면 자동으로 진단 대상에 포함됩니다.

interface GuildFeature {
  featureKey: string;
  scope: 'guild';
  permissions: bigint[];
}

interface ChannelFeature {
  featureKey: string;
  scope: 'channel';
  permissions: bigint[];
  /** DB에서 채널 ID 목록을 가져오는 함수 */
  resolveChannelIds: (guildId: string) => Promise<string[]>;
}

interface HierarchyFeature {
  featureKey: string;
  scope: 'hierarchy';
  /** DB에서 점검이 필요한 역할 ID 목록을 가져오는 함수 */
  resolveTargetRoleIds: (guildId: string) => Promise<string[]>;
}

type FeatureDefinition = GuildFeature | ChannelFeature | HierarchyFeature;

const FEATURE_DEFINITIONS: FeatureDefinition[] = [
  // ── 1. 공통 (Basic) ──
  {
    featureKey: 'BASIC',
    scope: 'guild',
    permissions: [
      PermissionFlagsBits.ViewChannel,
      PermissionFlagsBits.SendMessages,
      PermissionFlagsBits.EmbedLinks,
    ],
  },

  // ── 2. 임시 음성 채널 (전역) ──
  {
    featureKey: 'VOICE_GLOBAL',
    scope: 'guild',
    permissions: [
      PermissionFlagsBits.ManageChannels,
      PermissionFlagsBits.MoveMembers,
      PermissionFlagsBits.Connect,
    ],
  },

  // ── 3. 임시 음성 채널 (생성기 채널 레벨) ──
  {
    featureKey: 'VOICE_GENERATOR_CHANNEL',
    scope: 'channel',
    permissions: [
      PermissionFlagsBits.ManageChannels,
      PermissionFlagsBits.MoveMembers,
      PermissionFlagsBits.ManageRoles,
      PermissionFlagsBits.Connect,
    ],
    resolveChannelIds: async (guildId) => {
      const generators = await prisma.voiceGenerator.findMany({ where: { guildId } });
      return generators.map((g: { channelId: string }) => g.channelId);
    },
  },

  // ── 4. 임시 음성 채널 (카테고리 레벨) ──
  {
    featureKey: 'VOICE_GENERATOR_CATEGORY',
    scope: 'channel',
    permissions: [
      PermissionFlagsBits.ManageChannels,
      PermissionFlagsBits.MoveMembers,
      PermissionFlagsBits.ManageRoles,
    ],
    resolveChannelIds: async (guildId) => {
      const generators = await prisma.voiceGenerator.findMany({ where: { guildId } });
      return generators
        .map((g: { categoryId: string | null }) => g.categoryId)
        .filter((id: string | null): id is string => id !== null);
    },
  },

  // ── 5. 메시지 흉내 (Mimic) ──
  {
    featureKey: 'MIMIC_WEBHOOK',
    scope: 'guild',
    permissions: [
      PermissionFlagsBits.ManageWebhooks,
      PermissionFlagsBits.ManageMessages,
    ],
  },

  // ↓ 향후 기능 추가 시 여기에 항목을 추가하세요 ↓
  // e.g., Audit Log Channel, Setup Wizard 등
];

// ─── Service ──────────────────────────────────────────────────────────────────

export class PermissionAuditService {
  public static async auditGuild(guild: Guild): Promise<AuditResult[]> {
    const botMember = guild.members.me;
    if (!botMember) return [];

    const results: AuditResult[] = [];

    for (const feature of FEATURE_DEFINITIONS) {
      try {
        if (feature.scope === 'guild') {
          results.push(this.checkGuildPermissions(botMember.permissions, feature));
        } else if (feature.scope === 'channel') {
          const channelIds = await feature.resolveChannelIds(guild.id);
          for (const channelId of channelIds) {
            const channel = await guild.channels.fetch(channelId).catch(() => null);
            if (channel) {
              results.push(this.checkChannelPermissions(channel, botMember, feature));
            }
          }
        } else if (feature.scope === 'hierarchy') {
          const roleIds = await feature.resolveTargetRoleIds(guild.id);
          for (const roleId of roleIds) {
            const role = guild.roles.cache.get(roleId) ?? await guild.roles.fetch(roleId).catch(() => null);
            if (role) {
              results.push(this.checkHierarchy(guild, botMember.roles.highest, role, feature.featureKey));
            }
          }
        }
      } catch (err) {
        logger.error(`PermissionAuditService: Error checking feature "${feature.featureKey}"`, err);
      }
    }

    return results;
  }

  // ── Private Helpers ────────────────────────────────────────────────────────

  private static checkGuildPermissions(
    currentPerms: Readonly<PermissionsBitField>,
    feature: GuildFeature,
  ): AuditResult {
    const missing = this.getMissing(currentPerms, feature.permissions);
    return {
      featureKey: feature.featureKey,
      status: missing.length > 0 ? 'FAIL' : 'SUCCESS',
      missingPermissions: missing,
      scope: 'guild',
    };
  }

  private static checkChannelPermissions(
    channel: GuildBasedChannel,
    botMember: NonNullable<Guild['members']['me']>,
    feature: ChannelFeature,
  ): AuditResult {
    const channelPerms = channel.permissionsFor(botMember);
    const missing = this.getMissing(channelPerms, feature.permissions);
    return {
      featureKey: feature.featureKey,
      status: missing.length > 0 ? 'WARNING' : 'SUCCESS',
      missingPermissions: missing,
      scope: 'channel',
      channelId: channel.id,
    };
  }

  private static checkHierarchy(
    guild: Guild,
    botHighestRole: Role,
    targetRole: Role,
    featureKey: string,
  ): AuditResult {
    const isOk = botHighestRole.position > targetRole.position;
    return {
      featureKey,
      status: isOk ? 'SUCCESS' : 'FAIL',
      missingPermissions: [],
      scope: 'hierarchy',
      hierarchyInfo: {
        botRolePosition: botHighestRole.position,
        targetRoleId: targetRole.id,
        targetRolePosition: targetRole.position,
        targetRoleName: targetRole.name,
      },
    };
  }

  private static getMissing(perms: Readonly<PermissionsBitField> | null, required: bigint[]): string[] {
    if (!perms) return required.map(this.permToString);
    return required.filter((p) => !perms.has(p)).map(this.permToString);
  }

  private static permToString(perm: bigint): string {
    return (
      Object.keys(PermissionFlagsBits).find(
        (key) => (PermissionFlagsBits as Record<string, bigint>)[key] === perm,
      ) ?? perm.toString()
    );
  }
}
