import {
  CROSS_AGENCY_MOVE_FORBIDDEN,
  ORG_AGENCY_MISMATCH,
  ORG_NOT_FOUND,
} from '@api/constants/admin.constant';
import {
  DISMATCH_PASSWORD,
  NOT_FOUND_FC,
  SAME_PASSWORD,
  SHOULD_BE_UNSUBSCRIBED,
} from '@api/constants/api.constant';
import { AgencyOrganizationRepository } from '@api/modules/agency/organization/organization.repository';
import { ExcelService } from '@lib/excel/src';
import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
import { compare } from 'bcrypt';
import { Response } from 'express';
import { SubscribeRepository } from '../subscribe/subscribe/subscribe.repository';
import { GetUserListDto } from '../user/user.dto';
import { UserService } from '../user/user.service';
import { CheckPasswordDto, GetFcListDto, GetPushConsentListDto, UpdateFcDto, UpdatePushSettingsDto } from './fc.dto';
import { MemberType } from './fc.entity';
import { FcRepository } from './fc.repository';
import { maskString } from '@api/helpers/mask.helper';

@Injectable()
export class FcService {
  //* 이벤트 팝업 [다음에] 스누즈 — Redis 키 96h TTL(계정별, 웹/앱 공유·자동만료). Redis 장애 시 fail-open(계속 노출).
  private readonly redis: Redis | null;
  private static readonly EVENT_SNOOZE_SEC = 96 * 60 * 60; //* 96시간

  constructor(
    private fcRepository: FcRepository,
    private userService: UserService,
    private excelService: ExcelService,
    private subscribeService: SubscribeRepository,
    //* 기업조직(task-688): 회원 수정 시 orgId 검증에 사용.
    private agencyOrganizationRepository: AgencyOrganizationRepository,
    private readonly config: ConfigService,
  ) {
    const url = this.config.get<string>('redisUrl');
    this.redis = url ? new Redis(url, { maxRetriesPerRequest: 1 }) : null;
  }

  private eventSnoozeKey(memId: number) {
    return `push-event-popup-snooze:${memId}`;
  }

  /** FC 유저 목록조회 */
  async getFcUserList(dto: GetUserListDto) {
    return await this.userService.getUserList(dto);
  }

  /** FC 목록조회 */
  async getFcList(dto: GetFcListDto) {
    const where = await this.fcRepository.whereClause(dto);
    const orderBy = await this.fcRepository.orderByClause(dto);
    const list = await this.fcRepository.getList(
      where,
      orderBy,
      dto.page,
      false,
      dto.limit,
    );
    const count = await this.fcRepository.count(where);

    return {
      count,
      list,
    };
  }

  /** FC 목록 엑셀다운로드 */
  async downloadFcList(dto: GetFcListDto, res: Response, role: string) {
    const where = await this.fcRepository.whereClause(dto);
    const orderBy = await this.fcRepository.orderByClause(dto);
    const data: any = await this.fcRepository.getList(
      where,
      orderBy,
      dto.page,
      true,
    );

    const dataGenerator: any =
      role === 'sales'
        ? data.map((e) => ({
            ...e,
            member: {
              memId: e.member.memId,
              memType: e.member.memType,
              memLoginId: e.member.memLoginId,
              memName: e.member.memName
                ? maskString('name', e.member.memName)
                : '',
              memPhone: e.member.memPhone
                ? maskString('phone', e.member.memPhone)
                : '',
              memEmail: e.member.memEmail
                ? maskString('email', e.member.memEmail)
                : '',
              memAgency: e.member.memAgency,
              memMyReferralCode: e.member.memMyReferralCode,
              referralCount: e.member.referralCount,
              memIsResigned: e.member.memIsResigned,
              memResignedAt: e.member.memResignedAt,
              memLastLoginAt: e.member.memLastLoginAt,
              memUpdatedAt: e.member.memUpdatedAt,
              memCreatedAt: e.member.memCreatedAt,
            },
          }))
        : data;

    const columns = this.buildExcelColumns(role);
    await this.excelService.generate(res, '목록', { dataGenerator, columns });
  }

  /** FC 상세 */
  async getDetail(memId: number) {
    return await this.fcRepository.getDetail(memId);
  }

  /** FC 정보수정 */
  async update(memId: number, dto: UpdateFcDto) {
    const fc = await this.fcRepository.getFcByMemId(memId);

    if (dto.memPassword && fc.memType === MemberType.일반FC) {
      const isSame = await compare(dto.memPassword, fc.memPassword);

      if (isSame) {
        throw new BadRequestException(SAME_PASSWORD);
      }
    }

    //* 기업조직(task-688): 기업회원의 memAgency는 deprecated.
    //* FE가 약속을 어기고 전송하더라도 BE가 정책을 강제하여 무시.
    if (fc.memType === MemberType.기업FC) {
      delete dto.memAgency;

      //* 기업조직(task-688): 기업FC 수정 시 orgId 정책 강제.
      //*  - dto에 orgId가 명시되었을 때만 검증(undefined는 변경 없음).
      //*  - null/빈문자열은 소속 해제 의도 → repository에서 null로 저장.
      //*  - 조직 미존재 → E0301, 다른 기업 조직 → E0309(동일 기업 외 이동 차단),
      //*    소속 정보 자체 불일치는 E0302.
      if (Object.prototype.hasOwnProperty.call(dto, 'orgId')) {
        const nextOrgId = dto.orgId;
        if (nextOrgId) {
          const org =
            await this.agencyOrganizationRepository.findOrgById(nextOrgId);
          if (!org) {
            throw new NotFoundException(ORG_NOT_FOUND);
          }
          if (!fc.agcId) {
            throw new BadRequestException(ORG_AGENCY_MISMATCH);
          }
          if (org.agcId !== fc.agcId) {
            throw new BadRequestException(CROSS_AGENCY_MOVE_FORBIDDEN);
          }
        }
      }
    }

    await this.fcRepository.update(memId, dto);
    return;
  }

  async checkPassword(memId: number, dto: CheckPasswordDto) {
    const { loginId, password } = dto;
    const fc = await this.fcRepository.getFcByMemIdAndLoginId(memId, loginId);
    //* mut:k8-checkPassword begin
    if (!fc) {
      throw new ForbiddenException(NOT_FOUND_FC);
    }
    //* mut:k8-checkPassword end
    const isPasswordMatch = await compare(password, fc.memPassword);

    if (!isPasswordMatch) {
      throw new BadRequestException(DISMATCH_PASSWORD);
    }

    return;
  }

  async withdraw(fcId: number) {
    const subscription =
      await this.subscribeService.checkSubscriptionStatus(fcId);
    if (subscription.isSubscribing) {
      throw new BadRequestException(SHOULD_BE_UNSUBSCRIBED);
    }

    await this.fcRepository.withdraw(fcId);
    return;
  }

  async getAgencyIdByMember(memId: number) {
    return await this.fcRepository.getAgencyFc(memId);
  }

  async getAgencyByAltCode(altCode: string) {
    return await this.fcRepository.getAgencyByAltCode(altCode);
  }

  async getAgencyByAgcCode(agcCode: string) {
    return await this.fcRepository.getAgencyByAgcCode(agcCode);
  }

  //*--------------------------------------------- excel ---------------------------------------------//
  /** 엑셀 다운로드 컬럼 */
  buildExcelColumns(role: string): { header: string; key: string }[] {
    if (role === 'sales') {
      return [
        { header: '영업채널명', key: 'salesAgencyChannel.sacName' },
        { header: '구독상태', key: 'excelSubscribe.subState' },
        { header: '회원아이디', key: 'member.memLoginId' },
        { header: '회원명', key: 'member.memName' },
        { header: '소속명', key: 'member.memAgency' },
        { header: '연락처', key: 'member.memPhone' },
        { header: '이메일', key: 'member.memEmail' },
        { header: '추천인코드', key: 'member.memMyReferralCode' },
        { header: '추천가입수', key: 'member.referralCount' },
        { header: '탈퇴여부', key: 'member.memIsResigned' },
        { header: '탈퇴일', key: 'member.memResignedAt' },
        { header: '가입일', key: 'member.memCreatedAt' },
      ];
    } else {
      return [
        { header: '영업대리점명', key: 'salesAgency.sagName' },
        { header: '영업채널명', key: 'salesAgencyChannel.sacName' },
        { header: '구독상태', key: 'excelSubscribe.subState' },
        { header: '회원아이디', key: 'member.memLoginId' },
        { header: '회원명', key: 'member.memName' },
        { header: '소속명', key: 'member.memAgency' },
        { header: '연락처', key: 'member.memPhone' },
        { header: '이메일', key: 'member.memEmail' },
        { header: '추천인코드', key: 'member.memMyReferralCode' },
        { header: '추천가입수', key: 'member.referralCount' },
        { header: '탈퇴여부', key: 'member.memIsResigned' },
        { header: '탈퇴일', key: 'member.memResignedAt' },
        { header: '가입일', key: 'member.memCreatedAt' },
      ];
    }
  }

  //*--------------------------------------------- schdule ---------------------------------------------//
  async deleteFc() {
    return await this.fcRepository.delete();
  }

  //*--------------------------------------------- 추천가입내역 ---------------------------------------------//
  async getReferralSignupList(memId: number, page: number, limit?: number) {
    const list = await this.fcRepository.getReferralSignupList(
      memId,
      page,
      limit,
    );
    const count = await this.fcRepository.getReferralSignupCount(memId);

    return {
      list,
      count,
    };
  }

  //*--------------------------------------------- 푸시알림 설정 ---------------------------------------------//
  async getPushSettings(memId: number) {
    const data = await this.fcRepository.getPushSettings(memId);
    //* 이벤트 팝업 [다음에] 스누즈(96h) 여부 — 키 존재하면 아직 스누즈 중(웹/앱 공유).
    let eventPopupSnoozed = false;
    if (this.redis) {
      eventPopupSnoozed = !!(await this.redis
        .exists(this.eventSnoozeKey(memId))
        .catch(() => 0));
    }
    return { ...(data ?? {}), eventPopupSnoozed };
  }

  //* [다음에] — 96시간 스누즈 세팅(계정별). Redis 없으면 무동작(계속 노출 = fail-open).
  async snoozeEventPopup(memId: number) {
    if (!this.redis) return;
    await this.redis
      .set(this.eventSnoozeKey(memId), '1', 'EX', FcService.EVENT_SNOOZE_SEC)
      .catch(() => undefined);
  }

  async updatePushSettings(memId: number, dto: UpdatePushSettingsDto) {
    await this.fcRepository.updatePushSettings(memId, dto);
    return;
  }

  //*--------------------------------------------- 회원푸시동의 관리 (관리자용) ---------------------------------------------//
  async getPushConsentList(dto: GetPushConsentListDto) {
    const list = await this.fcRepository.getPushConsentList(dto);
    const count = await this.fcRepository.getPushConsentCount(dto);

    return {
      count,
      list,
    };
  }
}
