import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ClientSecretCredential, UsernamePasswordCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import { TokenCredentialAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials';
import {
  EntraNotConfiguredException,
  EntraAuthenticationFailedException,
  EntraInsufficientPrivilegesException,
  GraphApiException,
} from './entra.exceptions';

export interface EntraUser {
  id: string;
  userPrincipalName: string;
  displayName: string;
  givenName?: string;
  surname?: string;
  mail?: string;
  mailNickname?: string;
  jobTitle?: string;
  department?: string;
  officeLocation?: string;
  mobilePhone?: string;
  businessPhones?: string[];
  employeeId?: string;
  accountEnabled: boolean;
  userType?: string;
  manager?: {
    id: string;
    displayName: string;
    mail?: string;
    userPrincipalName?: string;
  };
}

@Injectable()
export class EntraService {
  private readonly logger = new Logger(EntraService.name);
  private client: Client | null = null;

  constructor(private configService: ConfigService) {}

  /**
   * 检查 Entra ID 是否已配置
   */
  isEnabled(): boolean {
    const tenantId = this.configService.get<string>('AZURE_TENANT_ID');
    const clientId = this.configService.get<string>('AZURE_CLIENT_ID');
    const clientSecret = this.configService.get<string>('AZURE_CLIENT_SECRET');

    return !!(tenantId && clientId && clientSecret);
  }

  /**
   * 获取配置的同步组 ID（如果有）
   */
  getSyncGroupId(): string | undefined {
    return this.configService.get<string>('AZURE_ENTRA_SYNC_GROUP_ID');
  }

  /**
   * 创建 Graph Client
   */
  private getClient(): Client {
    if (this.client) {
      return this.client;
    }

    const tenantId = this.configService.get<string>('AZURE_TENANT_ID');
    const clientId = this.configService.get<string>('AZURE_CLIENT_ID');
    const clientSecret = this.configService.get<string>('AZURE_CLIENT_SECRET');

    // 检查缺失的配置
    const missingConfig: string[] = [];
    if (!tenantId) missingConfig.push('AZURE_TENANT_ID');
    if (!clientId) missingConfig.push('AZURE_CLIENT_ID');
    if (!clientSecret) missingConfig.push('AZURE_CLIENT_SECRET');

    if (missingConfig.length > 0) {
      throw new EntraNotConfiguredException(missingConfig);
    }

    try {
      const credential = new ClientSecretCredential(tenantId!, clientId!, clientSecret!);

      const authProvider = new TokenCredentialAuthenticationProvider(credential, {
        scopes: ['https://graph.microsoft.com/.default'],
      });

      this.client = Client.initWithMiddleware({
        authProvider: authProvider,
      });

      return this.client;
    } catch (error) {
      this.logger.error('Failed to create Graph API client:', error.message);
      throw new EntraAuthenticationFailedException(error.message);
    }
  }

  /**
   * 用 ROPC 验证用户密码（cloud-only Entra 用户的登录入口）。
   *
   * 前置条件（IT 必须配，否则全员 fail）：
   * 1) App Registration → Authentication → Allow public client flows = Yes
   * 2) Conditional Access policy 把这个 app 从强制 MFA 里 exclude
   * 详见 .learnings/2026-05-04-entra-ropc-it-setup.md
   *
   * 失败一律返回 false，不抛——让 auth.service 的 fallback 链决定下一步。
   */
  async authenticatePassword(upn: string, password: string): Promise<boolean> {
    if (!this.isEnabled()) {
      this.logger.warn('Entra ROPC skipped: AZURE_TENANT_ID/CLIENT_ID not configured');
      return false;
    }
    if (!upn || !password) return false;

    const tenantId = this.configService.get<string>('AZURE_TENANT_ID')!;
    const clientId = this.configService.get<string>('AZURE_CLIENT_ID')!;

    try {
      const credential = new UsernamePasswordCredential(tenantId, clientId, upn, password);
      await credential.getToken('https://graph.microsoft.com/.default');
      this.logger.log(`Entra ROPC authentication successful for: ${upn}`);
      return true;
    } catch (error: any) {
      const msg = String(error?.message || '');
      // AADSTS 错误码分类——SRE 看日志能直接判断是配置问题还是凭据问题
      if (msg.includes('AADSTS50126')) {
        this.logger.warn(`Entra ROPC: invalid password for ${upn}`);
      } else if (
        msg.includes('AADSTS50076') ||
        msg.includes('AADSTS50079') ||
        msg.includes('AADSTS50072')
      ) {
        // 应用层 MFA bypass：Microsoft 已确认密码正确，仅因 MFA 策略拒发 token。
        // 我们仅在本 app 内信任这个判断，不影响 Outlook/Teams 等其它服务的 MFA。
        // 决策原因 + 安全边界详见 .learnings/2026-05-04-entra-ropc-it-setup.md
        this.logger.log(
          `Entra ROPC: password verified for ${upn} (MFA required by tenant, accepted at app layer)`,
        );
        return true;
      } else if (msg.includes('AADSTS50034')) {
        this.logger.warn(`Entra ROPC: user ${upn} not found in tenant`);
      } else if (msg.includes('AADSTS7000218')) {
        this.logger.error(
          `Entra ROPC: app missing "Allow public client flows" — fix App Registration`,
        );
      } else if (msg.includes('AADSTS50158') || msg.includes('AADSTS50053')) {
        this.logger.warn(`Entra ROPC: ${upn} blocked (federated/locked): ${msg.slice(0, 200)}`);
      } else {
        this.logger.warn(`Entra ROPC failed for ${upn}: ${msg.slice(0, 300)}`);
      }
      return false;
    }
  }

  /**
   * 获取所有用户（启用的成员用户，且有部门信息）
   */
  async getAllUsers(): Promise<EntraUser[]> {
    this.logger.log('📥 Fetching users from Entra ID...');

    const client = this.getClient();
    const users: EntraUser[] = [];

    try {
      const selectFields = [
        'id',
        'userPrincipalName',
        'displayName',
        'givenName',
        'surname',
        'mail',
        'jobTitle',
        'department',
        'officeLocation',
        'mobilePhone',
        'businessPhones',
        'employeeId',
        'accountEnabled',
        'userType',
      ].join(',');

      // 构建过滤条件：启用 + 成员用户
      const filterQuery = "accountEnabled eq true and userType eq 'Member'";

      let request = client
        .api('/users')
        .select(selectFields)
        .filter(filterQuery)
        .expand('manager($select=id,displayName,mail,userPrincipalName)')
        .top(999);

      let response = await request.get();

      // 客户端二次过滤和处理
      const processUsers = (userArray: any[]): EntraUser[] => {
        return userArray
          .filter((user: any) => {
            // 排除没有 userPrincipalName 或 mail 的账户
            if (!user.userPrincipalName && !user.mail) {
              return false;
            }
            // 排除 B2B 访客用户
            if (user.userPrincipalName?.includes('#EXT#')) {
              return false;
            }
            // 排除系统邮箱
            if (
              user.displayName?.startsWith('SystemMailbox{') ||
              user.displayName?.startsWith('DiscoverySearchMailbox{')
            ) {
              return false;
            }
            // 排除没有 displayName 的账户
            if (!user.displayName) {
              return false;
            }
            // 排除没有 department 的账户
            if (!user.department) {
              return false;
            }

            return true;
          })
          .map((user: any) => ({
            ...user,
            manager: user.manager
              ? {
                  id: user.manager.id,
                  displayName: user.manager.displayName,
                  mail: user.manager.mail,
                  userPrincipalName: user.manager.userPrincipalName,
                }
              : undefined,
          }));
      };

      users.push(...processUsers(response.value));

      // 处理分页
      while (response['@odata.nextLink']) {
        this.logger.log(`   Fetched ${users.length} users, continuing...`);
        response = await client.api(response['@odata.nextLink']).get();
        users.push(...processUsers(response.value));
      }

      this.logger.log(`✅ Successfully fetched ${users.length} users from Entra ID`);
      return users;
    } catch (error) {
      this.logger.error('❌ Failed to fetch users from Entra ID:', error.message);
      
      // 处理特定错误
      if (error.statusCode === 401) {
        throw new EntraAuthenticationFailedException('Invalid credentials or token expired');
      } else if (error.statusCode === 403) {
        throw new EntraInsufficientPrivilegesException(['User.Read.All']);
      } else {
        throw new GraphApiException(error.message, { statusCode: error.statusCode, body: error.body });
      }
    }
  }


  /**
   * 获取指定组的成员用户
   * @param groupId - 组的 ID
   */
  async getGroupMembers(groupId: string): Promise<EntraUser[]> {
    this.logger.log(`📥 Fetching members of group ${groupId} from Entra ID...`);

    const client = this.getClient();
    const users: EntraUser[] = [];

    try {
      const selectFields = [
        'id',
        'userPrincipalName',
        'displayName',
        'givenName',
        'surname',
        'mail',
        'jobTitle',
        'department',
        'officeLocation',
        'mobilePhone',
        'businessPhones',
        'employeeId',
        'accountEnabled',
        'userType',
      ].join(',');

      // 获取组成员
      // 注意：不在服务端使用 filter，因为 /groups/{id}/members 端点的过滤器支持有限
      let request = client
        .api(`/groups/${groupId}/members`)
        .select(selectFields)
        .top(999);

      let response = await request.get();

      // 客户端过滤和处理
      const processUsers = (userArray: any[]): EntraUser[] => {
        return userArray
          .filter((user: any) => {
            // 只保留用户对象（排除组、设备等）
            if (!user['@odata.type'] || !user['@odata.type'].includes('user')) {
              return false;
            }
            // 只保留成员用户（排除访客）
            if (user.userType && user.userType !== 'Member') {
              return false;
            }
            // 排除没有 userPrincipalName 或 mail 的账户
            if (!user.userPrincipalName && !user.mail) {
              return false;
            }
            // 排除 B2B 访客用户
            if (user.userPrincipalName?.includes('#EXT#')) {
              return false;
            }
            // 排除系统邮箱
            if (
              user.displayName?.startsWith('SystemMailbox{') ||
              user.displayName?.startsWith('DiscoverySearchMailbox{')
            ) {
              return false;
            }
            // 排除没有 displayName 的账户
            if (!user.displayName) {
              return false;
            }

            return true;
          })
          .map((user: any) => ({
            id: user.id,
            userPrincipalName: user.userPrincipalName,
            displayName: user.displayName,
            givenName: user.givenName,
            surname: user.surname,
            mail: user.mail,
            jobTitle: user.jobTitle,
            department: user.department,
            officeLocation: user.officeLocation,
            mobilePhone: user.mobilePhone,
            businessPhones: user.businessPhones,
            employeeId: user.employeeId,
            accountEnabled: user.accountEnabled,
            userType: user.userType,
          }));
      };

      users.push(...processUsers(response.value));

      // 处理分页
      while (response['@odata.nextLink']) {
        this.logger.log(`   Fetched ${users.length} users, continuing...`);
        response = await client.api(response['@odata.nextLink']).get();
        users.push(...processUsers(response.value));
      }

      // 获取每个用户的 manager 信息
      for (const user of users) {
        try {
          const managerResponse = await client
            .api(`/users/${user.id}/manager`)
            .select('id,displayName,mail,userPrincipalName')
            .get();
          
          if (managerResponse) {
            user.manager = {
              id: managerResponse.id,
              displayName: managerResponse.displayName,
              mail: managerResponse.mail,
              userPrincipalName: managerResponse.userPrincipalName,
            };
          }
        } catch (error) {
          // Manager 不存在时忽略错误
          this.logger.debug(`No manager found for user ${user.mail}`);
        }
      }

      this.logger.log(`✅ Successfully fetched ${users.length} users from group ${groupId}`);
      return users;
    } catch (error) {
      this.logger.error(`❌ Failed to fetch group members from Entra ID:`, error.message);
      
      // 处理特定错误
      if (error.statusCode === 401) {
        throw new EntraAuthenticationFailedException('Invalid credentials or token expired');
      } else if (error.statusCode === 403) {
        throw new EntraInsufficientPrivilegesException(['GroupMember.Read.All', 'User.Read.All']);
      } else if (error.statusCode === 404) {
        throw new GraphApiException(`Group ${groupId} not found`, { statusCode: 404 });
      } else {
        throw new GraphApiException(error.message, { statusCode: error.statusCode, body: error.body });
      }
    }
  }

  /**
   * 按邮箱精确查询用户（v1.3 自动入组判定用，select 含 mailNickname）。
   * 找不到返回 null；其他错误（401/403/5xx）抛业务异常，由调用方处理。
   */
  async getUserByEmail(email: string): Promise<EntraUser | null> {
    const trimmed = email?.trim();
    if (!trimmed) return null;

    const client = this.getClient();
    const escaped = trimmed.replace(/'/g, "''");

    try {
      const response = await client
        .api('/users')
        .select(
          'id,userPrincipalName,displayName,mail,mailNickname,accountEnabled,userType',
        )
        .filter(`mail eq '${escaped}'`)
        .top(1)
        .get();

      const raw = response?.value?.[0];
      if (!raw) return null;

      return {
        id: raw.id,
        userPrincipalName: raw.userPrincipalName,
        displayName: raw.displayName,
        mail: raw.mail,
        mailNickname: raw.mailNickname,
        accountEnabled: raw.accountEnabled,
        userType: raw.userType,
      };
    } catch (error: any) {
      if (error.statusCode === 401) {
        throw new EntraAuthenticationFailedException('Invalid credentials or token expired');
      }
      if (error.statusCode === 403) {
        throw new EntraInsufficientPrivilegesException(['User.Read.All']);
      }
      throw new GraphApiException(error.message, {
        statusCode: error.statusCode,
        body: error.body,
      });
    }
  }

  /**
   * 把用户加入 Entra 安全组（v1.3 自动入组）。
   * 需要 Application permission: GroupMember.ReadWrite.All（FF + AIxC 已 admin consent）。
   * 调用 POST /groups/{groupId}/members/$ref，返回 204 No Content。
   * 已是组成员时 Graph 返回 400 with code "Request_BadRequest"，按业务无害处理（视为成功）。
   */
  async addUserToGroup(groupId: string, userId: string): Promise<{ alreadyMember: boolean }> {
    const client = this.getClient();
    try {
      await client.api(`/groups/${groupId}/members/$ref`).post({
        '@odata.id': `https://graph.microsoft.com/v1.0/directoryObjects/${userId}`,
      });
      return { alreadyMember: false };
    } catch (error: any) {
      const message: string = error.message || '';
      const errBody = error.body || '';
      const isAlreadyMember =
        error.statusCode === 400 &&
        (message.includes('already exist') || String(errBody).includes('already exist'));
      if (isAlreadyMember) {
        return { alreadyMember: true };
      }
      if (error.statusCode === 401) {
        throw new EntraAuthenticationFailedException('Invalid credentials or token expired');
      }
      if (error.statusCode === 403) {
        throw new EntraInsufficientPrivilegesException(['GroupMember.ReadWrite.All']);
      }
      if (error.statusCode === 404) {
        throw new GraphApiException(`Group ${groupId} or user ${userId} not found`, {
          statusCode: 404,
        });
      }
      throw new GraphApiException(message, {
        statusCode: error.statusCode,
        body: error.body,
      });
    }
  }

  /**
   * 测试连接
   */
  async testConnection(): Promise<boolean> {
    try {
      const client = this.getClient();
      await client.api('/users').top(1).get();
      this.logger.log('✅ Entra ID connection successful');
      return true;
    } catch (error) {
      this.logger.error('❌ Entra ID connection failed:', error.message);
      return false;
    }
  }
}
