import { Injectable, Logger } from '@nestjs/common';
import { Users } from '../couchdb-datasources/users';
import { RedmineDataLoader } from '../redmine-data-loader/redmine-data-loader';
import { RedmineUserCacheWriterService } from '../user-cache-writer/user-cache-writer.service';
import { RedmineTypes } from '../models/redmine-types';
import { MemoryCache } from '../utils/memory-cache';

export const USER_MEMORY_CACHE_LIFETIME = 24 * 60 * 60 * 1000;
const USER_MEMORY_CACHE_AUTOCLEAN_INTERVAL = 1000 * 60 * 5;

@Injectable()
export class UsersService {
  private logger = new Logger(UsersService.name);
  private memoryCache = new MemoryCache<number, RedmineTypes.User>(
    USER_MEMORY_CACHE_LIFETIME,
    USER_MEMORY_CACHE_AUTOCLEAN_INTERVAL,
  );

  constructor(
    private users: Users,
    private redmineDataLoader: RedmineDataLoader,
    private redmineUserCacheWriterService: RedmineUserCacheWriterService,
  ) {}

  async getUser(userId: number): Promise<RedmineTypes.PublicUser> {
    const userFromMemoryCache = this.getUserFromMemoryCache(userId);
    if (userFromMemoryCache) {
      return RedmineTypes.CreatePublicUserFromUser(userFromMemoryCache);
    }
    const userFromCache = await this.getUserFromCache(userId);
    if (userFromCache) {
      this.memoryCache.set(userId, userFromCache);
      return RedmineTypes.CreatePublicUserFromUser(userFromCache);
    }
    let userFromRedmine = await this.getUserFromRedmine(userId);
    if (userFromRedmine) {
      userFromRedmine = await this.redmineUserCacheWriterService.saveUser(
        userFromRedmine,
      );
    }
    return RedmineTypes.CreatePublicUserFromUser(
      this.memoryCache.set(
        userId,
        userFromRedmine || RedmineTypes.Unknown.user,
      ),
    );
  }

  async findUserByLogin(login: string): Promise<RedmineTypes.User | null> {
    const userFromMemoryCache = this.memoryCache.find((item) => {
      const email = item.mail;
      if (!email) return false;
      return email.startsWith(login);
    });
    if (userFromMemoryCache) {
      return RedmineTypes.CreateUser(userFromMemoryCache);
    }
    const usersDb = await this.users.getDatasource();
    const res = await usersDb.find({
      selector: {
        mail: {
          $regex: login,
        },
      },
      limit: 1,
    });
    if (!res || !res.docs || !res.docs[0]) {
      return null;
    }
    const userFromDb = res.docs[0];
    this.memoryCache.set(userFromDb.id, userFromDb);
    return RedmineTypes.CreateUser(userFromDb);
  }

  async findUserByName(
    firstname: string,
    lastname: string,
  ): Promise<RedmineTypes.User | null> {
    const userFromMemoryCache = this.memoryCache.find((item) => {
      return item.firstname === firstname && item.lastname === lastname;
    });
    if (userFromMemoryCache) {
      return RedmineTypes.CreateUser(userFromMemoryCache);
    }
    const usersDb = await this.users.getDatasource();
    const res = await usersDb.find({
      selector: {
        firstname: firstname,
        lastname: lastname,
      },
      limit: 1,
    });
    if (!res || !res.docs || !res.docs[0]) {
      return null;
    }
    const userFromDb = res.docs[0];
    this.memoryCache.set(userFromDb.id, userFromDb);
    return RedmineTypes.CreateUser(userFromDb);
  }

  async findUserByFullname(
    fullname: string,
  ): Promise<RedmineTypes.User | null> {
    const parts = fullname.split(' ').map((item) => item.trim());
    const lastname = parts.splice(parts.length - 1, 1).join(' ');
    const firstname = parts.join(' ');
    return await this.findUserByName(firstname, lastname);
  }

  private async getUserFromRedmine(
    userId: number,
  ): Promise<RedmineTypes.User | null> {
    const user = await this.redmineDataLoader.loadUser(userId);
    if (user) {
      this.logger.debug(
        `Get user from redmine with userId = ${userId}, login = ${user.login}`,
      );
    }
    return user;
  }

  private async getUserFromCache(
    userId: number,
  ): Promise<RedmineTypes.User | null> {
    const usersDb = await this.users.getDatasource();
    try {
      const user = (await usersDb.get(String(userId))) as any;
      this.logger.debug(
        `Get user from couchdb with userId = ${userId}, login = ${user.login}`,
      );
      return user;
    } catch (ex) {
      return null;
    }
  }

  private getUserFromMemoryCache(userId: number): RedmineTypes.User | null {
    const user = this.memoryCache.get(userId);
    if (user) {
      this.logger.debug(
        `Get user from memory cache with userId = ${userId}, login = ${user.login}`,
      );
    }
    return user;
  }
}
