import bcrypt from 'bcrypt';
import type { IUserRepository, SafeUser } from '../repositories/user.repository.js';
import { CreateUserSchema, PasswordSchema } from '../validation/user.schema.js';
import { NotFoundError, ConflictError } from './mcp-server.service.js';

const SALT_ROUNDS = 10;

export class UserService {
  constructor(private readonly userRepo: IUserRepository) {}

  async list(): Promise<SafeUser[]> {
    return this.userRepo.findAll();
  }

  async getById(id: string): Promise<SafeUser> {
    const user = await this.userRepo.findById(id);
    if (user === null) {
      throw new NotFoundError(`User not found: ${id}`);
    }
    return user;
  }

  async getByEmail(email: string): Promise<SafeUser> {
    const user = await this.userRepo.findByEmail(email);
    if (user === null) {
      throw new NotFoundError(`User not found: ${email}`);
    }
    return user;
  }

  async create(input: unknown): Promise<SafeUser> {
    const data = CreateUserSchema.parse(input);

    const existing = await this.userRepo.findByEmail(data.email);
    if (existing !== null) {
      throw new ConflictError(`User already exists: ${data.email}`);
    }

    const passwordHash = await bcrypt.hash(data.password, SALT_ROUNDS);

    const createData: { email: string; passwordHash: string; name?: string } = {
      email: data.email,
      passwordHash,
    };
    if (data.name !== undefined) {
      createData.name = data.name;
    }

    return this.userRepo.create(createData);
  }

  async delete(id: string): Promise<void> {
    await this.getById(id);
    await this.userRepo.delete(id);
  }

  /** Verify a plaintext password against the stored hash for a user id. */
  async verifyPassword(id: string, password: string): Promise<boolean> {
    const user = await this.userRepo.findByIdWithHash(id);
    if (user === null) {
      throw new NotFoundError(`User not found: ${id}`);
    }
    // Locked accounts (e.g. system user with `!locked`) never verify.
    if (!user.passwordHash || user.passwordHash.startsWith('!') || user.passwordHash.startsWith('__')) {
      return false;
    }
    return bcrypt.compare(password, user.passwordHash);
  }

  /** Hash + store a new password for a user. Throws NotFoundError if absent. */
  async setPassword(id: string, newPassword: string): Promise<void> {
    PasswordSchema.parse(newPassword);
    await this.getById(id); // 404 if missing
    const passwordHash = await bcrypt.hash(newPassword, SALT_ROUNDS);
    await this.userRepo.update(id, { passwordHash });
  }

  async count(): Promise<number> {
    return this.userRepo.count();
  }

  // ── Backup/restore helpers ──

  async upsertByEmail(data: Record<string, unknown>): Promise<SafeUser> {
    const email = data['email'] as string;
    const existing = await this.userRepo.findByEmail(email);

    if (existing !== null) {
      // Update name/role but never overwrite passwordHash
      const updateFields: { name?: string; role?: string } = {};
      if (data['name'] !== undefined) updateFields.name = data['name'] as string;
      if (data['role'] !== undefined) updateFields.role = data['role'] as string;
      if (Object.keys(updateFields).length > 0) {
        return this.userRepo.update(existing.id, updateFields);
      }
      return existing;
    }

    // New user — use placeholder passwordHash
    const createData: { email: string; passwordHash: string; name?: string; role?: string } = {
      email,
      passwordHash: '__RESTORED__',
    };
    if (data['name'] !== undefined) createData.name = data['name'] as string;
    if (data['role'] !== undefined) createData.role = data['role'] as string;

    return this.userRepo.create(createData);
  }

  async deleteByEmail(email: string): Promise<void> {
    const existing = await this.userRepo.findByEmail(email);
    if (existing === null) return;
    await this.userRepo.delete(existing.id);
  }
}
