import { Inject, ILogger, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
import { NotificationUserEntity } from '../entity/user';
import { NotificationMessageLogEntity } from '../entity/log';
import {
  NotificationBatchSendRequest,
  NotificationBatchSendResult,
  NotificationProvider,
  NotificationSendRequest,
  NotificationSendResult,
  ResolvedNotificationRecipient,
} from '../types';
import {
  FeishuDepartmentItem,
  FeishuDepartmentUserItem,
  FeishuNotificationProvider,
} from './providers/feishu';
import {
  DEFAULT_FEISHU_APP_CONFIG,
  DEFAULT_NOTIFICATION_PLATFORM,
} from './constants';

@Provide()
@Scope(ScopeEnum.Singleton)
export class NotificationService {
  @Inject()
  logger: ILogger;

  @Inject()
  feishuProvider: FeishuNotificationProvider;

  @InjectEntityModel(NotificationUserEntity)
  notificationUserEntity: Repository<NotificationUserEntity>;

  @InjectEntityModel(NotificationMessageLogEntity)
  notificationMessageLogEntity: Repository<NotificationMessageLogEntity>;

  async sendBatch(
    request: NotificationBatchSendRequest
  ): Promise<NotificationBatchSendResult> {
    if (!request.targets || request.targets.length === 0) {
      throw new Error('targets不能为空');
    }

    const items: Array<NotificationSendResult & { index: number }> = [];

    for (let i = 0; i < request.targets.length; i++) {
      const target = request.targets[i];
      try {
        const result = await this.send({
          platform: request.platform || DEFAULT_NOTIFICATION_PLATFORM,
          title: target.title || request.title,
          message: target.message || request.message || '',
          messageType: request.messageType || 'text',
          mentionAll: target.mentionAll ?? request.mentionAll,
          receiveId: target.receiveId,
          receiveIdType: target.receiveIdType,
          targetUser: target.targetUser,
          appId: request.appId,
          appSecret: request.appSecret,
          sourceType: request.sourceType || 'api',
          sourceId: target.sourceId,
          sourceName: request.sourceName,
          metadata: {
            ...(request.metadata || {}),
            ...(target.metadata || {}),
          },
        });
        items.push({ index: i, ...result });
      } catch (e) {
        items.push({
          index: i,
          success: false,
          platform: request.platform || DEFAULT_NOTIFICATION_PLATFORM,
          requestId: '',
          errorMessage: e.message,
          resolvedRecipient: {
            receiveId: target.receiveId || '',
            receiveIdType: target.receiveIdType || 'open_id',
          },
        });
      }
    }

    const successCount = items.filter(item => item.success).length;
    const failCount = items.length - successCount;
    return {
      success: failCount === 0,
      total: items.length,
      successCount,
      failCount,
      items,
    };
  }

  async send(
    request: NotificationSendRequest
  ): Promise<NotificationSendResult> {
    if (!request.message?.trim()) {
      throw new Error('message不能为空');
    }

    const platform = request.platform || DEFAULT_NOTIFICATION_PLATFORM;
    const requestId = uuidv4();
    const resolvedRecipient = await this.resolveRecipient({
      ...request,
      platform,
    });
    const provider = this.getProvider(platform);

    const logEntity = await this.notificationMessageLogEntity.save({
      requestId,
      platform,
      appId: request.appId,
      messageType: request.messageType || 'text',
      receiveIdType: resolvedRecipient.receiveIdType,
      receiveId: resolvedRecipient.receiveId,
      bizUserId: request.targetUser?.bizUserId,
      externalUserId:
        request.targetUser?.externalUserId ||
        String(resolvedRecipient.userSnapshot?.externalUserId || ''),
      openId:
        request.targetUser?.openId ||
        String(resolvedRecipient.userSnapshot?.openId || ''),
      chatId:
        request.targetUser?.chatId ||
        String(resolvedRecipient.userSnapshot?.chatId || ''),
      title: request.title,
      message: request.message,
      sourceType: request.sourceType || 'api',
      sourceId: request.sourceId,
      sourceName: request.sourceName,
      status: 'pending',
      requestPayload: request,
      metadata: request.metadata,
    } as any);

    const providerResult = await provider.send({
      requestId,
      platform,
      appId: request.appId,
      appSecret: request.appSecret,
      message: request.message,
      title: request.title,
      messageType: request.messageType || 'text',
      mentionAll: request.mentionAll,
      receiveId: resolvedRecipient.receiveId,
      receiveIdType: resolvedRecipient.receiveIdType,
      metadata: request.metadata,
    });

    await this.notificationMessageLogEntity.update(logEntity.id, {
      status: providerResult.success ? 'success' : 'failed',
      externalMessageId: providerResult.externalMessageId,
      providerCode:
        providerResult.providerCode !== undefined
          ? String(providerResult.providerCode)
          : null,
      providerMessage: providerResult.providerMessage,
      errorMessage: providerResult.errorMessage,
      providerPayload: providerResult.providerPayload,
      providerResponse: providerResult.providerResponse,
    } as any);

    return {
      ...providerResult,
      requestId,
      resolvedRecipient,
    };
  }

  async syncFeishuUsersFromRoot(options?: {
    rootDepartmentId?: string;
    departmentIdType?: 'department_id' | 'open_department_id';
    userIdType?: 'open_id' | 'union_id' | 'user_id';
    appId?: string;
    appSecret?: string;
  }) {
    const rootDepartmentId = options?.rootDepartmentId || '0';
    const departmentIdType = options?.departmentIdType || 'department_id';
    const userIdType = options?.userIdType || 'open_id';
    const appId = options?.appId || DEFAULT_FEISHU_APP_CONFIG.appId;
    const appSecret = options?.appSecret || DEFAULT_FEISHU_APP_CONFIG.appSecret;

    this.logger.info(
      `[Notification][SyncFeishu] ????: rootDepartmentId=${rootDepartmentId}, departmentIdType=${departmentIdType}, userIdType=${userIdType}`
    );

    const departments = await this.collectFeishuDepartments({
      rootDepartmentId,
      departmentIdType,
      appId,
      appSecret,
    });

    this.logger.info(
      `[Notification][SyncFeishu] ???????: count=${departments.length}`
    );

    const syncResult = {
      platform: 'feishu',
      rootDepartmentId,
      departmentCount: departments.length,
      userCount: 0,
      createdCount: 0,
      updatedCount: 0,
      departments: departments.map(item => ({
        departmentId: item.department_id,
        openDepartmentId: item.open_department_id,
        name: item.name,
        parentDepartmentId: item.parent_department_id,
      })),
    };

    const rootUsers = await this.collectFeishuDepartmentUsers({
      departmentId: rootDepartmentId,
      departmentIdType,
      userIdType,
      appId,
      appSecret,
    });
    this.logger.info(
      `[Notification][SyncFeishu] ?????????: departmentId=${rootDepartmentId}, count=${rootUsers.length}`
    );
    for (const user of rootUsers) {
      const action = await this.upsertFeishuUser(user, { appId });
      syncResult.userCount += 1;
      if (action === 'created') {
        syncResult.createdCount += 1;
      } else {
        syncResult.updatedCount += 1;
      }
    }

    for (const department of departments) {
      const currentDepartmentId =
        department.open_department_id || department.department_id || '';
      const currentDepartmentIdType = department.open_department_id
        ? 'open_department_id'
        : 'department_id';
      this.logger.info(
        `[Notification][SyncFeishu] ????????: departmentId=${currentDepartmentId}, departmentIdType=${currentDepartmentIdType}, name=${
          department.name || ''
        }`
      );
      const users = await this.collectFeishuDepartmentUsers({
        departmentId: currentDepartmentId,
        departmentIdType: currentDepartmentIdType,
        userIdType,
        appId,
        appSecret,
      });
      this.logger.info(
        `[Notification][SyncFeishu] ????????: departmentId=${currentDepartmentId}, count=${users.length}`
      );

      for (const user of users) {
        const action = await this.upsertFeishuUser(user, {
          appId,
          department,
        });
        syncResult.userCount += 1;
        if (action === 'created') {
          syncResult.createdCount += 1;
        } else {
          syncResult.updatedCount += 1;
        }
      }
    }

    this.logger.info(
      `[Notification][SyncFeishu] ????: departmentCount=${syncResult.departmentCount}, userCount=${syncResult.userCount}, created=${syncResult.createdCount}, updated=${syncResult.updatedCount}`
    );

    return syncResult;
  }

  private getProvider(platform: string): NotificationProvider {
    const providers: NotificationProvider[] = [this.feishuProvider];
    const provider = providers.find(item => item.platform === platform);
    if (!provider) {
      throw new Error(`暂不支持的通知平台: ${platform}`);
    }
    return provider;
  }

  private async resolveRecipient(
    request: NotificationSendRequest
  ): Promise<ResolvedNotificationRecipient> {
    if (request.receiveId) {
      return {
        receiveId: request.receiveId,
        receiveIdType: request.receiveIdType || 'open_id',
      };
    }

    const targetUser = request.targetUser || {};
    const where: Record<string, any> = {
      platform: request.platform,
      status: 1,
    };

    const lookupFields: Array<keyof typeof targetUser> = [
      'bizUserId',
      'externalUserId',
      'openId',
      'unionId',
      'userKey',
      'chatId',
      'email',
      'mobile',
      'name',
    ];

    let user: NotificationUserEntity;
    for (const field of lookupFields) {
      const value = targetUser[field];
      if (!value) continue;
      user = await this.notificationUserEntity.findOne({
        where: { ...where, [field]: value },
      });
      if (user) {
        return this.buildRecipientFromUser(user, request.receiveIdType);
      }
    }

    throw new Error(
      '缺少接收者信息，请提供receiveId，或在notification_user表中维护平台用户映射'
    );
  }

  private buildRecipientFromUser(
    user: NotificationUserEntity,
    preferredType?: string
  ): ResolvedNotificationRecipient {
    const candidateTypes = preferredType
      ? [preferredType]
      : ['open_id', 'user_id', 'chat_id', 'union_id', 'email', 'mobile'];

    for (const type of candidateTypes) {
      const value = this.pickReceiveIdByType(user, type);
      if (value) {
        return {
          receiveId: value,
          receiveIdType: type,
          userSnapshot: {
            id: user.id,
            bizUserId: user.bizUserId,
            externalUserId: user.externalUserId,
            openId: user.openId,
            chatId: user.chatId,
            name: user.name,
          },
        };
      }
    }

    throw new Error(
      `通知用户映射存在但缺少可用接收标识: userId=${user.id}, platform=${user.platform}`
    );
  }

  private pickReceiveIdByType(user: NotificationUserEntity, type: string) {
    switch (type) {
      case 'open_id':
        return user.openId;
      case 'user_id':
        return user.externalUserId;
      case 'chat_id':
        return user.chatId;
      case 'union_id':
        return user.unionId;
      case 'email':
        return user.email;
      case 'mobile':
        return user.mobile;
      default:
        return undefined;
    }
  }

  private async collectFeishuDepartments(options: {
    rootDepartmentId: string;
    departmentIdType: 'department_id' | 'open_department_id';
    appId: string;
    appSecret: string;
  }) {
    const visited = new Set<string>();
    const collected: FeishuDepartmentItem[] = [];

    const visit = async (
      departmentId: string,
      departmentIdType: 'department_id' | 'open_department_id'
    ) => {
      let pageToken: string | undefined;
      do {
        this.logger.info(
          `[Notification][SyncFeishu] ?????: departmentId=${departmentId}, departmentIdType=${departmentIdType}, pageToken=${
            pageToken || ''
          }`
        );
        const page = await this.feishuProvider.listDepartmentChildren({
          departmentId,
          departmentIdType,
          pageToken,
          pageSize: 50,
          fetchChild: false,
          appId: options.appId,
          appSecret: options.appSecret,
        });
        this.logger.info(
          `[Notification][SyncFeishu] ?????: departmentId=${departmentId}, departmentIdType=${departmentIdType}, count=${page.items.length}, hasMore=${page.hasMore}`
        );

        for (const item of page.items) {
          const nextDepartmentId =
            item.open_department_id || item.department_id;
          const nextDepartmentIdType = item.open_department_id
            ? 'open_department_id'
            : 'department_id';
          if (!nextDepartmentId || visited.has(nextDepartmentId)) {
            continue;
          }
          visited.add(nextDepartmentId);
          collected.push(item);
          this.logger.info(
            `[Notification][SyncFeishu] ????: departmentId=${
              item.department_id || ''
            }, openDepartmentId=${item.open_department_id || ''}, name=${
              item.name || ''
            }`
          );
          await visit(nextDepartmentId, nextDepartmentIdType);
        }

        pageToken = page.hasMore ? page.pageToken : undefined;
      } while (pageToken);
    };

    await visit(options.rootDepartmentId, options.departmentIdType);
    return collected;
  }

  private async collectFeishuDepartmentUsers(options: {
    departmentId: string;
    departmentIdType: 'department_id' | 'open_department_id';
    userIdType: 'open_id' | 'union_id' | 'user_id';
    appId: string;
    appSecret: string;
  }) {
    const collected: FeishuDepartmentUserItem[] = [];
    let pageToken: string | undefined;

    do {
      this.logger.info(
        `[Notification][SyncFeishu] ??????: departmentId=${
          options.departmentId
        }, pageToken=${pageToken || ''}`
      );
      const page = await this.feishuProvider.listUsersByDepartment({
        departmentId: options.departmentId,
        departmentIdType: options.departmentIdType,
        userIdType: options.userIdType,
        pageToken,
        pageSize: 50,
        appId: options.appId,
        appSecret: options.appSecret,
      });
      this.logger.info(
        `[Notification][SyncFeishu] ??????: departmentId=${options.departmentId}, count=${page.items.length}, hasMore=${page.hasMore}`
      );
      if (page.items.length > 0) {
        const sample = page.items[0];
        this.logger.info(
          `[Notification][SyncFeishu] ????: departmentId=${
            options.departmentId
          }, name=${sample.name || ''}, mobile=${sample.mobile || ''}, openId=${
            sample.open_id || ''
          }, userId=${sample.user_id || ''}`
        );
      }
      collected.push(...page.items);
      pageToken = page.hasMore ? page.pageToken : undefined;
    } while (pageToken);

    return collected;
  }

  private async upsertFeishuUser(
    user: FeishuDepartmentUserItem,
    options: {
      appId: string;
      department?: FeishuDepartmentItem;
    }
  ): Promise<'created' | 'updated'> {
    const existing = await this.findExistingFeishuUser(user);
    const metadata = {
      avatar: user.avatar,
      status: user.status,
      departmentIds: user.department_ids || [],
      leaderUserId: user.leader_user_id,
      city: user.city,
      country: user.country,
      workStation: user.work_station,
      joinTime: user.join_time,
      department: options.department
        ? {
            departmentId: options.department.department_id,
            openDepartmentId: options.department.open_department_id,
            name: options.department.name,
            parentDepartmentId: options.department.parent_department_id,
          }
        : undefined,
    };

    if (existing) {
      await this.notificationUserEntity.update(existing.id, {
        platform: 'feishu',
        appId: options.appId,
        externalUserId: user.user_id,
        openId: user.open_id,
        unionId: user.union_id,
        name: user.name,
        mobile: user.mobile,
        email: user.email,
        status: 1,
        metadata,
      } as any);
      return 'updated';
    }

    await this.notificationUserEntity.save({
      platform: 'feishu',
      appId: options.appId,
      externalUserId: user.user_id,
      openId: user.open_id,
      unionId: user.union_id,
      name: user.name,
      mobile: user.mobile,
      email: user.email,
      status: 1,
      metadata,
    } as any);
    return 'created';
  }

  private async findExistingFeishuUser(user: FeishuDepartmentUserItem) {
    if (user.open_id) {
      const existing = await this.notificationUserEntity.findOne({
        where: { platform: 'feishu', openId: user.open_id },
      });
      if (existing) return existing;
    }
    if (user.user_id) {
      const existing = await this.notificationUserEntity.findOne({
        where: { platform: 'feishu', externalUserId: user.user_id },
      });
      if (existing) return existing;
    }
    if (user.union_id) {
      const existing = await this.notificationUserEntity.findOne({
        where: { platform: 'feishu', unionId: user.union_id },
      });
      if (existing) return existing;
    }
    if (user.mobile) {
      const existing = await this.notificationUserEntity.findOne({
        where: { platform: 'feishu', mobile: user.mobile },
      });
      if (existing) return existing;
    }
    if (user.email) {
      const existing = await this.notificationUserEntity.findOne({
        where: { platform: 'feishu', email: user.email },
      });
      if (existing) return existing;
    }
    return null;
  }
}

