import { Test, TestingModule } from '@nestjs/testing';
import { UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import { AdminService } from '../admin.service';
import { PrismaService } from '../../../prisma/prisma.service';

// Mock bcrypt
jest.mock('bcryptjs', () => ({
  compare: jest.fn(),
  hash: jest.fn(),
}));

describe('AdminService', () => {
  let service: AdminService;
  let prisma: PrismaService;

  const mockPrisma = {
    adminUser: {
      findUnique: jest.fn(),
      update: jest.fn(),
    },
    user: { count: jest.fn() },
    course: { count: jest.fn() },
    content: { count: jest.fn() },
    prompt: { count: jest.fn() },
    order: { count: jest.fn() },
    adminLog: { create: jest.fn() },
  };

  const mockJwtService = {
    sign: jest.fn().mockReturnValue('mock-jwt-token'),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AdminService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: JwtService, useValue: mockJwtService },
      ],
    }).compile();

    service = module.get<AdminService>(AdminService);
    prisma = module.get<PrismaService>(PrismaService);
    jest.clearAllMocks();
  });

  describe('login', () => {
    const mockAdmin = {
      id: 1,
      username: 'admin',
      passwordHash: '$2a$10$hashed',
      role: { name: 'superadmin' },
      status: 'ACTIVE',
    };

    it('should login successfully', async () => {
      mockPrisma.adminUser.findUnique.mockResolvedValue(mockAdmin);
      jest.spyOn(bcrypt, 'compare').mockResolvedValue(true as never);
      mockPrisma.adminUser.update.mockResolvedValue(mockAdmin);

      const result = await service.login('admin', 'admin123456');
      
      expect(result).toEqual(expect.objectContaining({ id: 1, username: 'admin', role: 'superadmin' }));
      expect(mockPrisma.adminUser.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 1 },
          data: { lastLoginAt: expect.any(Date) },
        })
      );
    });

    it('should throw on wrong password', async () => {
      mockPrisma.adminUser.findUnique.mockResolvedValue(mockAdmin);
      jest.spyOn(bcrypt, 'compare').mockResolvedValue(false as never);

      await expect(service.login('admin', 'wrongpass')).rejects.toThrow(UnauthorizedException);
    });

    it('should throw when admin not found', async () => {
      mockPrisma.adminUser.findUnique.mockResolvedValue(null);

      await expect(service.login('unknown', 'pass')).rejects.toThrow(UnauthorizedException);
    });

    it('should throw when admin is inactive', async () => {
      mockPrisma.adminUser.findUnique.mockResolvedValue({ ...mockAdmin, status: 'INACTIVE' });

      await expect(service.login('admin', 'pass')).rejects.toThrow(UnauthorizedException);
    });
  });

  describe('getDashboard', () => {
    it('should return all stats', async () => {
      mockPrisma.user.count.mockResolvedValue(100);
      mockPrisma.course.count.mockResolvedValue(20);
      mockPrisma.content.count.mockResolvedValue(45);
      mockPrisma.prompt.count.mockResolvedValue(30);
      mockPrisma.order.count.mockResolvedValue(15);

      const result = await service.getDashboard();

      expect(result).toEqual({
        stats: {
          userCount: 100,
          courseCount: 20,
          contentCount: 45,
          promptCount: 30,
          orderCount: 15,
        },
      });
    });

    it('should return zeroes when no data', async () => {
      mockPrisma.user.count.mockResolvedValue(0);
      mockPrisma.course.count.mockResolvedValue(0);
      mockPrisma.content.count.mockResolvedValue(0);
      mockPrisma.prompt.count.mockResolvedValue(0);
      mockPrisma.order.count.mockResolvedValue(0);

      const result = await service.getDashboard();

      expect(result).toEqual({
        stats: {
          userCount: 0, courseCount: 0, contentCount: 0,
          promptCount: 0, orderCount: 0,
        },
      });
    });

    it('should filter deleted users and courses', async () => {
      mockPrisma.user.count.mockResolvedValue(50);
      mockPrisma.course.count.mockResolvedValue(10);
      mockPrisma.content.count.mockResolvedValue(5);
      mockPrisma.prompt.count.mockResolvedValue(3);
      mockPrisma.order.count.mockResolvedValue(2);

      await service.getDashboard();

      expect(mockPrisma.user.count).toHaveBeenCalledWith({ where: { deletedAt: null } });
      expect(mockPrisma.course.count).toHaveBeenCalledWith({ where: { deletedAt: null } });
      expect(mockPrisma.content.count).toHaveBeenCalledWith({
        where: { deletedAt: null, status: 'PUBLISHED' },
      });
    });
  });

  describe('logAction', () => {
    it('should create an admin log entry', async () => {
      const mockLog = { id: 1, adminId: 1, action: 'login', target: null, detail: null, ip: null };
      mockPrisma.adminLog.create.mockResolvedValue(mockLog);

      const result = await service.logAction(1, 'login');

      expect(result).toEqual(mockLog);
      expect(mockPrisma.adminLog.create).toHaveBeenCalledWith({
        data: { adminId: 1, action: 'login', target: undefined, detail: undefined, ip: undefined },
      });
    });

    it('should create log with full details', async () => {
      mockPrisma.adminLog.create.mockResolvedValue({ id: 2 });

      await service.logAction(1, 'update', 'courses/1', '修改课程标题', '127.0.0.1');

      expect(mockPrisma.adminLog.create).toHaveBeenCalledWith({
        data: { adminId: 1, action: 'update', target: 'courses/1', detail: '修改课程标题', ip: '127.0.0.1' },
      });
    });
  });
});
