import {
  Injectable,
  Logger,
  type OnApplicationBootstrap,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ILike, IsNull, LessThan, Repository } from 'typeorm';
import { User } from './entities/user.entity';

// 이메일 인증 도입 컷오프 — 이 시각 이전에 생성된 로컬 계정만 그랜드페더링 대상.
// 이후 가입은 항상 정상 인증 절차를 거치므로 토큰 상태와 무관하게 자동 인증되지 않는다.
const LEGACY_VERIFY_CUTOFF = new Date('2026-06-20T00:00:00Z');

// 외부 노출용 사용자 형태 (passwordHash 등 민감정보 제외)
export interface PublicUser {
  id: string;
  email: string;
  name: string;
  role: string | null;
  avatarUrl: string | null;
}

// 사용자 데이터 접근 계층 — 인증 모듈이 주입하여 사용
@Injectable()
export class UserService implements OnApplicationBootstrap {
  private readonly logger = new Logger(UserService.name);

  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  // 이메일 인증 도입 이전에 만들어진 로컬 계정 그랜드페더링 —
  // 컷오프 이전 생성 + 인증 토큰이 없는 로컬 계정만 인증 완료로 간주.
  // 컷오프 이후 가입은 토큰 상태와 무관하게 대상에서 제외되어, 정상 인증 절차를 항상 거친다.
  // 멱등(idempotent): 최초 1회만 실제로 갱신되고 이후에는 대상이 없다.
  async onApplicationBootstrap(): Promise<void> {
    const result = await this.userRepository.update(
      {
        provider: 'local',
        emailVerified: false,
        emailVerifyTokenHash: IsNull(),
        createdAt: LessThan(LEGACY_VERIFY_CUTOFF),
      },
      { emailVerified: true },
    );
    if (result.affected && result.affected > 0) {
      this.logger.log(
        `이메일 인증 도입 전 기존 로컬 계정 ${result.affected}건을 인증 완료로 보정`,
      );
    }
  }

  // id 단건 조회 (없으면 null)
  findById(id: string): Promise<User | null> {
    return this.userRepository.findOne({ where: { id } });
  }

  // 이메일로 조회 (가입 중복 체크용)
  findByEmail(email: string): Promise<User | null> {
    return this.userRepository.findOne({ where: { email } });
  }

  // 사용자 검색/목록 (멤버 초대 자동완성용) — 이메일/이름 부분일치(대소문자 무시).
  // 검색어가 없으면 전체 목록(이름순, limit 개)을 반환한다.
  search(query: string | undefined, limit = 50): Promise<User[]> {
    const q = (query ?? '').trim();
    if (!q) {
      return this.userRepository.find({ order: { name: 'ASC' }, take: limit });
    }
    const like = ILike(`%${q}%`);
    return this.userRepository.find({
      where: [{ email: like }, { name: like }],
      order: { name: 'ASC' },
      take: limit,
    });
  }

  // 로그인 검증용 — passwordHash 를 명시적으로 포함하여 조회
  findByEmailWithPassword(email: string): Promise<User | null> {
    return this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.passwordHash')
      .where('user.email = :email', { email })
      .getOne();
  }

  // 이메일 인증 토큰 해시로 조회 — 인증 토큰 컬럼은 select:false 라 명시적으로 포함.
  findByEmailVerifyTokenHash(tokenHash: string): Promise<User | null> {
    return this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.emailVerifyTokenHash')
      .addSelect('user.emailVerifyExpiresAt')
      .where('user.emailVerifyTokenHash = :tokenHash', { tokenHash })
      .getOne();
  }

  // 신규 로컬 사용자 생성 (비밀번호는 해시된 상태로 전달받음, 기본 미인증)
  create(payload: {
    email: string;
    passwordHash: string;
    name: string;
    role?: string | null;
  }): Promise<User> {
    const user = this.userRepository.create({
      email: payload.email,
      passwordHash: payload.passwordHash,
      name: payload.name,
      role: payload.role ?? null,
      provider: 'local',
      emailVerified: false,
    });
    return this.userRepository.save(user);
  }

  // 미인증(가입 대기) 로컬 계정 재가입 — 비밀번호/이름/역할을 새 입력으로 갱신.
  // (인증 완료 전 계정에 한해 호출; 직후 인증 메일을 재발송한다)
  async updateRegistration(
    userId: string,
    payload: { passwordHash: string; name: string; role?: string | null },
  ): Promise<void> {
    await this.userRepository.update(userId, {
      passwordHash: payload.passwordHash,
      name: payload.name,
      role: payload.role ?? null,
    });
  }

  // 소셜(OAuth) 사용자 생성 — 비밀번호 없음, 제공자가 이메일을 검증하므로 인증 완료 상태.
  createOAuthUser(payload: {
    email: string;
    name: string;
    provider: string;
    avatarUrl?: string | null;
  }): Promise<User> {
    const user = this.userRepository.create({
      email: payload.email,
      passwordHash: null,
      name: payload.name,
      provider: payload.provider,
      avatarUrl: payload.avatarUrl ?? null,
      emailVerified: true,
    });
    return this.userRepository.save(user);
  }

  // 이메일 인증 토큰(해시)·만료 시각 저장
  async setEmailVerifyToken(
    userId: string,
    tokenHash: string,
    expiresAt: Date,
  ): Promise<void> {
    await this.userRepository.update(userId, {
      emailVerifyTokenHash: tokenHash,
      emailVerifyExpiresAt: expiresAt,
    });
  }

  // 이메일 인증 완료 처리 — 인증 플래그 true, 토큰 폐기
  async markEmailVerified(userId: string): Promise<void> {
    await this.userRepository.update(userId, {
      emailVerified: true,
      emailVerifyTokenHash: null,
      emailVerifyExpiresAt: null,
    });
  }

  // 비밀번호 재설정 토큰(해시)·만료 시각 저장
  async setPasswordResetToken(
    userId: string,
    tokenHash: string,
    expiresAt: Date,
  ): Promise<void> {
    await this.userRepository.update(userId, {
      passwordResetTokenHash: tokenHash,
      passwordResetExpiresAt: expiresAt,
    });
  }

  // 비밀번호 재설정 토큰 해시로 조회 — 토큰 컬럼은 select:false 라 명시적으로 포함.
  findByPasswordResetTokenHash(tokenHash: string): Promise<User | null> {
    return this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.passwordResetTokenHash')
      .addSelect('user.passwordResetExpiresAt')
      .where('user.passwordResetTokenHash = :tokenHash', { tokenHash })
      .getOne();
  }

  // 비밀번호 변경 — 새 해시 저장 + 재설정 토큰 폐기
  async updatePassword(userId: string, passwordHash: string): Promise<void> {
    await this.userRepository.update(userId, {
      passwordHash,
      passwordResetTokenHash: null,
      passwordResetExpiresAt: null,
    });
  }

  // 엔티티 → 외부 노출용 형태로 변환 (민감정보 제거)
  static toPublic(user: User): PublicUser {
    return {
      id: user.id,
      email: user.email,
      name: user.name,
      role: user.role,
      avatarUrl: user.avatarUrl ?? null,
    };
  }
}
