import { ServiceUnavailableException } from '@nestjs/common';
import type {
  DingTalkGroupParams,
  DingTalkGroupSummary,
  DingTalkGroupUpdateParams,
  DingTalkServiceContext,
} from './dingtalk.types';

export class DingTalkGroupClient {
  constructor(private readonly context: DingTalkServiceContext) {}

  /** 创建排班制考勤组 */
  async createAttendanceGroup(params: DingTalkGroupParams): Promise<number> {
    if (!(await this.context.isConfigured())) throw new ServiceUnavailableException('钉钉未配置');
    const token = await this.context.getAccessToken();

    const topGroup = this.buildAttendanceGroupBody(params);
    const body = { op_user_id: params.owner, top_group: topGroup };

    await this.context.rateLimit();
    const res = await fetch(
      `https://oapi.dingtalk.com/topapi/attendance/group/add?access_token=${token}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      },
    );
    const data = (await res.json()) as {
      errcode: number; errmsg: string;
      result?: { id: number };
    };
    if (data.errcode !== 0) {
      throw new Error(`钉钉创建考勤组失败: ${data.errmsg} (code=${data.errcode})`);
    }
    this.context.logger.log(`钉钉考勤组创建成功: ${params.name} (id=${data.result?.id})`);
    return data.result!.id;
  }

  /** 更新排班制考勤组，确保复用考勤组时同步最新打卡限制 */
  async updateAttendanceGroup(params: DingTalkGroupUpdateParams): Promise<void> {
    if (!(await this.context.isConfigured())) throw new ServiceUnavailableException('钉钉未配置');
    const token = await this.context.getAccessToken();
    const topGroup = { ...this.buildAttendanceGroupBody(params), id: params.id };

    await this.context.rateLimit();
    const res = await fetch(
      `https://oapi.dingtalk.com/topapi/attendance/group/modify?access_token=${token}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ op_user_id: params.owner, top_group: topGroup }),
      },
    );
    const data = (await res.json()) as {
      errcode?: number;
      errmsg?: string;
      success?: boolean;
      message?: string;
    };
    const succeeded = data.success === true || data.errcode === 0;
    if (!succeeded) {
      throw new Error(
        `钉钉更新考勤组失败: ${data.message || data.errmsg || '未知错误'} ` +
          `(code=${data.errcode ?? 'unknown'})`,
      );
    }
    this.context.logger.log(`钉钉考勤组更新成功: ${params.name} (id=${params.id})`);
  }

  private buildAttendanceGroupBody(params: DingTalkGroupParams): Record<string, unknown> {
    const machineOnly = params.attendance_machine_only ?? false;
    const topGroup: Record<string, unknown> = {
      name: params.name,
      type: params.type,
      owner: params.owner,
      members: params.members.map((m) => ({
        role: m.role,
        type: m.type,
        user_id: m.user_id,
      })),
      enable_emp_select_class: machineOnly ? false : (params.enable_emp_select_class ?? true),
      disable_check_without_schedule: machineOnly ? true : (params.disable_check_without_schedule ?? false),
      disable_check_when_rest: params.disable_check_when_rest ?? true,
    };
    if (params.shift_ids?.length) {
      topGroup.shift_vo_list = params.shift_ids.map((id) => ({ id }));
    }
    if (machineOnly) {
      Object.assign(topGroup, {
        enable_outside_check: false,
        enable_position_ble: false,
        positions: [],
        wifis: [],
      });
    }
    return topGroup;
  }

  /** 查询所有考勤组摘要（分页，每页10条） */
  async queryAttendanceGroups(_opUserId = 'manager'): Promise<DingTalkGroupSummary[]> {
    if (!(await this.context.isConfigured())) throw new ServiceUnavailableException('钉钉未配置');
    const token = await this.context.getAccessToken();

    const all: DingTalkGroupSummary[] = [];
    let offset = 0;
    let hasMore = true;

    while (hasMore) {
      await this.context.rateLimit();
      const res = await fetch(
        `https://oapi.dingtalk.com/topapi/attendance/getsimplegroups?access_token=${token}`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ offset, size: 10 }),
        },
      );
      const data = (await res.json()) as {
        errcode: number; errmsg: string;
        result?: {
          has_more: boolean;
          groups: Array<{ group_id: number; group_name: string; type: string; member_count: number }>;
        };
      };
      if (data.errcode !== 0) {
        throw new Error(`钉钉查询考勤组失败: ${data.errmsg} (code=${data.errcode})`);
      }
      if (data.result?.groups) {
        all.push(...data.result.groups.map((g) => ({
          group_id: g.group_id,
          group_name: g.group_name,
          type: g.type,
          member_count: g.member_count,
        })));
      }
      hasMore = data.result?.has_more ?? false;
      offset += 10;
    }
    return all;
  }

  async deleteAttendanceGroup(groupId: number, opUserId = 'manager'): Promise<void> {
    if (!(await this.context.isConfigured())) throw new ServiceUnavailableException('钉钉未配置');
    const token = await this.context.getAccessToken();

    await this.context.rateLimit();
    const keyResponse = await fetch(
      `https://oapi.dingtalk.com/topapi/attendance/groups/idtokey?access_token=${token}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ op_user_id: opUserId, group_id: groupId }),
      },
    );
    const keyData = await keyResponse.json() as {
      errcode: number;
      errmsg: string;
      result?: string;
    };
    if (keyData.errcode !== 0 || !keyData.result) {
      throw new Error(`钉钉考勤组ID转换失败: ${keyData.errmsg} (code=${keyData.errcode})`);
    }

    await this.context.rateLimit();
    const deleteResponse = await fetch(
      `https://oapi.dingtalk.com/topapi/attendance/group/delete?access_token=${token}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ op_userid: opUserId, group_key: keyData.result }),
      },
    );
    const deleteData = await deleteResponse.json() as {
      errcode: number;
      errmsg: string;
      success?: boolean;
    };
    if (deleteData.errcode !== 0 || deleteData.success !== true) {
      throw new Error(`钉钉删除考勤组失败: ${deleteData.errmsg} (code=${deleteData.errcode})`);
    }
  }
}
