import {
  BadRequestException,
  ConflictException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import jwt from 'jsonwebtoken';
import { Role } from '../../generated/prisma/enums.js';
import { MESSAGES } from '../../common/messages.js';
import { PrismaService } from '../../prisma.service.js';
import { UsersService } from '../../users/users.service.js';
import { MiniappPhoneLoginConfirmDto } from '../dto/miniapp-phone-login-confirm.dto.js';
import { MiniappPhoneLoginDto } from '../dto/miniapp-phone-login.dto.js';
import { WechatMiniAppService } from '../wechat-miniapp/wechat-miniapp.service.js';

type LoginTicketPayload = {
  purpose: 'MINIAPP_B_LOGIN_TICKET';
  phone: string;
  openId: string;
  userIds: number[];
};

/**
 * 小程序登录服务：承载 B/C 端手机号快捷登录链路。
 */
@Injectable()
export class MiniAppAuthService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly usersService: UsersService,
    private readonly wechatMiniAppService: WechatMiniAppService,
  ) {}

  /**
   * B 端手机号登录：单账号直接签发，多账号返回候选列表。
   */
  async loginForB(dto: MiniappPhoneLoginDto) {
    const identity = await this.wechatMiniAppService.resolvePhoneIdentity(
      dto.loginCode,
      dto.phoneCode,
    );
    const accounts = await this.prisma.user.findMany({
      where: { phone: identity.phone },
      select: {
        id: true,
        name: true,
        phone: true,
        openId: true,
        role: true,
        hospitalId: true,
        departmentId: true,
        groupId: true,
        hospital: {
          select: {
            id: true,
            name: true,
          },
        },
      },
      orderBy: [{ hospitalId: 'asc' }, { id: 'asc' }],
    });

    if (accounts.length === 0) {
      throw new NotFoundException(MESSAGES.AUTH.MINIAPP_NO_MATCHED_USER);
    }

    if (accounts.length === 1) {
      const [user] = accounts;
      await this.usersService.bindOpenIdForMiniAppLogin(
        user.id,
        identity.openId,
      );
      return this.usersService.loginByUserId(user.id);
    }

    return {
      needSelect: true,
      loginTicket: this.signLoginTicket({
        purpose: 'MINIAPP_B_LOGIN_TICKET',
        phone: identity.phone,
        openId: identity.openId,
        userIds: accounts.map((account) => account.id),
      }),
      accounts: accounts.map((account) => ({
        id: account.id,
        name: account.name,
        role: account.role,
        hospitalId: account.hospitalId,
        hospitalName: account.hospital?.name ?? null,
        departmentId: account.departmentId,
        groupId: account.groupId,
      })),
    };
  }

  /**
   * B 端多账号确认登录。
   */
  async confirmLoginForB(dto: MiniappPhoneLoginConfirmDto) {
    const payload = this.verifyLoginTicket(dto.loginTicket);
    if (!payload.userIds.includes(dto.userId)) {
      throw new BadRequestException(
        MESSAGES.AUTH.MINIAPP_ACCOUNT_SELECTION_INVALID,
      );
    }

    await this.usersService.bindOpenIdForMiniAppLogin(
      dto.userId,
      payload.openId,
    );
    return this.usersService.loginByUserId(dto.userId);
  }

  /**
   * C 端手机号登录：要求手机号唯一命中患者档案。
   */
  async loginForC(dto: MiniappPhoneLoginDto) {
    const identity = await this.wechatMiniAppService.resolvePhoneIdentity(
      dto.loginCode,
      dto.phoneCode,
    );
    const matchedPatients = await this.prisma.patient.findMany({
      where: { phone: identity.phone },
      select: { id: true },
      take: 2,
    });
    if (matchedPatients.length === 0) {
      throw new NotFoundException(
        MESSAGES.AUTH.FAMILY_PHONE_NOT_LINKED_PATIENT,
      );
    }
    if (matchedPatients.length > 1) {
      throw new ConflictException(
        MESSAGES.AUTH.FAMILY_PHONE_LINKED_MULTI_PATIENTS,
      );
    }

    const existingByOpenId = await this.prisma.familyMiniAppAccount.findUnique({
      where: { openId: identity.openId },
      select: { id: true, phone: true },
    });
    if (existingByOpenId && existingByOpenId.phone !== identity.phone) {
      throw new ConflictException(
        MESSAGES.AUTH.MINIAPP_OPEN_ID_BOUND_OTHER_FAMILY,
      );
    }

    const current = await this.prisma.familyMiniAppAccount.findUnique({
      where: { phone: identity.phone },
      select: {
        id: true,
        phone: true,
        openId: true,
        serviceUid: true,
      },
    });

    if (current?.openId && current.openId !== identity.openId) {
      throw new ConflictException(
        MESSAGES.AUTH.MINIAPP_OPEN_ID_BOUND_OTHER_FAMILY,
      );
    }

    const familyAccount = current
      ? await this.prisma.familyMiniAppAccount.update({
          where: { id: current.id },
          data: {
            openId: current.openId ?? identity.openId,
            lastLoginAt: new Date(),
          },
          select: {
            id: true,
            phone: true,
            openId: true,
            serviceUid: true,
          },
        })
      : await this.prisma.familyMiniAppAccount.create({
          data: {
            phone: identity.phone,
            openId: identity.openId,
            lastLoginAt: new Date(),
          },
          select: {
            id: true,
            phone: true,
            openId: true,
            serviceUid: true,
          },
        });

    return {
      tokenType: 'Bearer',
      accessToken: this.signFamilyAccessToken(familyAccount.id),
      familyAccount,
    };
  }

  /**
   * 短期登录票据签发。
   */
  private signLoginTicket(payload: LoginTicketPayload) {
    const secret = this.requireAuthSecret();
    return jwt.sign(payload, secret, {
      algorithm: 'HS256',
      expiresIn: '5m',
      issuer: 'tyt-api-nest',
    });
  }

  /**
   * 校验短期登录票据。
   */
  private verifyLoginTicket(token: string): LoginTicketPayload {
    const secret = this.requireAuthSecret();
    let payload: string | jwt.JwtPayload;

    try {
      payload = jwt.verify(token, secret, {
        algorithms: ['HS256'],
        issuer: 'tyt-api-nest',
      });
    } catch {
      throw new UnauthorizedException(
        MESSAGES.AUTH.MINIAPP_LOGIN_TICKET_INVALID,
      );
    }

    if (
      typeof payload !== 'object' ||
      payload.purpose !== 'MINIAPP_B_LOGIN_TICKET' ||
      typeof payload.phone !== 'string' ||
      typeof payload.openId !== 'string' ||
      !Array.isArray(payload.userIds) ||
      payload.userIds.some(
        (item) => typeof item !== 'number' || !Number.isInteger(item),
      )
    ) {
      throw new UnauthorizedException(
        MESSAGES.AUTH.MINIAPP_LOGIN_TICKET_INVALID,
      );
    }

    return payload as unknown as LoginTicketPayload;
  }

  /**
   * 签发 C 端访问 token。
   */
  private signFamilyAccessToken(accountId: number) {
    const secret = this.requireAuthSecret();
    return jwt.sign(
      {
        id: accountId,
        type: 'FAMILY_MINIAPP',
      },
      secret,
      {
        algorithm: 'HS256',
        expiresIn: '7d',
        issuer: 'tyt-api-nest-family',
      },
    );
  }

  /**
   * 读取认证密钥。
   */
  private requireAuthSecret() {
    const secret = process.env.AUTH_TOKEN_SECRET;
    if (!secret) {
      throw new UnauthorizedException(MESSAGES.AUTH.TOKEN_SECRET_MISSING);
    }
    return secret;
  }
}
