import { AuthTokenRepository } from '../repositories/AuthTokenRepository';
import { NotificationFactory } from '../factories/NotificationFactory';
import { TeamResolutionService } from '../domain/team/TeamResolutionService';
import { TeamShiftFacade } from '../facades/TeamShiftFacade';
import { MockNotificationFactory } from '../factories/NotificationFactory';
import { LeavesAppSettingsService } from '../services/LeavesAppSettingsService';
import { getYearMonth, todayInCompanyTz } from '../domain/leave/dateUtils';
import { getPrisma } from '../infrastructure/database/PrismaSingleton';
import { NotFoundError } from '../types/errors';

export class AuthFacade {
  constructor(
    private authRepo = new AuthTokenRepository(),
    private notifications: NotificationFactory,
    private teamService = new TeamResolutionService(),
    private appSettings = new LeavesAppSettingsService(),
    private teamShiftFacade = new TeamShiftFacade(undefined, new MockNotificationFactory()),
  ) {}

  async getOtpDebugStatus(): Promise<{ debugMode: boolean }> {
    return { debugMode: await this.appSettings.isOtpDebugEnabled() };
  }

  private nextMonthFrom(dateStr: string): string {
    const [y, m] = getYearMonth(dateStr).split('-').map(Number);
    const d = new Date(Date.UTC(m === 12 ? y + 1 : y, m === 12 ? 0 : m, 1));
    return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;
  }

  async getShiftPlanningConfig() {
    const today = todayInCompanyTz();
    const currentMonth = getYearMonth(today);
    const nextMonth = this.nextMonthFrom(today);
    const allowCurrentMonth = await this.appSettings.isShiftCurrentMonthEnabled();
    const minEditableMonth = allowCurrentMonth ? currentMonth : nextMonth;
    return {
      allowCurrentMonth,
      currentMonth,
      nextMonth,
      minEditableMonth,
      suggestedMonth: allowCurrentMonth ? currentMonth : nextMonth,
    };
  }

  async requestEmployeeToken(employeeId: string): Promise<{
    message: string;
    debugMode?: boolean;
    debugOtp?: string;
  }> {
    const prisma = getPrisma();
    const employee = await prisma.employees.findUnique({ where: { employee_id: employeeId } });
    if (!employee) throw new NotFoundError('Employee not found');
    if (employee.status !== 'active') throw new NotFoundError('Employee is inactive');

    const { token, expiresAt } = await this.authRepo.createOtp(employeeId);
    const debugMode = await this.appSettings.isOtpDebugEnabled();

    if (debugMode) {
      const minutes = Math.round((expiresAt.getTime() - Date.now()) / 60000);
      return {
        message: `Debug mode: OTP is shown on screen (valid ${minutes} min). Email was not sent.`,
        debugMode: true,
        debugOtp: token,
      };
    }

    const minutes = Math.round((expiresAt.getTime() - Date.now()) / 60000);
    await this.notifications.createEmail('OTP', {
      to: employee.email,
      code: token,
      minutes,
    }).send();

    return { message: 'OTP sent to registered email address', debugMode: false };
  }

  async employeeLogin(employeeId: string, otp: string) {
    const result = await this.authRepo.consumeOtp(otp);
    if (result.employeeId !== employeeId) {
      throw new NotFoundError('Invalid credentials');
    }
    const access_token = this.authRepo.createEmployeeJwt(result.employeeId);
    return {
      access_token,
      token_type: 'Bearer',
      expires_in: 28800,
      employeeId: result.employeeId,
      name: result.name,
    };
  }

  async hrLogin(username: string, password: string) {
    const result = await this.authRepo.hrLogin(username, password);
    return {
      access_token: result.token,
      token_type: 'Bearer',
      expires_in: 604800,
      user: result.user,
    };
  }

  async getEmployeeContext(employeeId: string) {
    const prisma = getPrisma();
    const employee = await prisma.employees.findUnique({ where: { employee_id: employeeId } });
    if (!employee) throw new NotFoundError('Employee not found');

    const managerLevel = await this.teamService.getEmployeeManagerLevel(employeeId);
    const isAdmin = managerLevel === 2;

    let teamId = '';
    let teamName = '';
    let leadEmployeeId: string | null = null;
    let isTeamLead = false;
    let allTeams: Array<{ teamId: string; teamName: string; isTeamLead: boolean }> = [];
    let members: Array<{
      employeeId: string;
      name: string;
      isManager: boolean;
      isAdmin: boolean;
      managerLevel: number;
      roleLabel: string;
    }> = [];
    let managers: Array<{ employeeId: string; name: string; email: string }> = [];

    if (isAdmin) {
      // Company admin may not be on any L10 team — still give full calendar/shift context
      allTeams = await this.teamService.listAllActiveTeams(employeeId);
      if (allTeams.length) {
        teamId = allTeams[0].teamId;
        teamName = allTeams[0].teamName;
        try {
          const primary = await this.teamService.resolveTeamById(teamId);
          leadEmployeeId = primary.leadBusinessId;
          isTeamLead = primary.leadBusinessId === employeeId;
        } catch {
          /* keep defaults */
        }
        members = await this.teamService.listVisibleTeamMembers(teamId, employeeId);
        managers = await this.teamService.listTeamManagers(teamId);
      }
    } else {
      const team = await this.teamService.resolveByBusinessId(employeeId);
      teamId = team.teamId;
      teamName = team.teamName;
      leadEmployeeId = team.leadBusinessId;
      isTeamLead = team.leadBusinessId === employeeId;
      allTeams = await this.teamService.listActiveTeamsForEmployee(employeeId);
      members = await this.teamService.listVisibleTeamMembers(team.teamId, employeeId);
      managers = await this.teamService.listTeamManagers(team.teamId);
    }

    const shiftPlanning = await this.getShiftPlanningConfig();
    const shiftTeams = await this.teamShiftFacade.listShiftTeamsForEmployee(
      employeeId,
      shiftPlanning.suggestedMonth,
    );

    return {
      employeeId,
      name: employee.name,
      teamId,
      teamName,
      teams: allTeams,
      shiftTeams: shiftTeams.setupTeams,
      isTeamLead,
      leadEmployeeId,
      managerLevel,
      isAdmin,
      isManager: managerLevel === 1,
      canManageTeamTiming: isAdmin || shiftTeams.setupTeams.some((t) => t.canManage),
      isShiftResponsible: shiftTeams.setupTeams.some((t) => t.canManage),
      shiftResponsibleMonth: shiftPlanning.suggestedMonth,
      shiftSetupTeams: shiftTeams.setupTeams,
      shiftReviewTeams: shiftTeams.reviewTeams,
      shiftVerifierTeams: shiftTeams.verifierTeams,
      shiftViewTeams: shiftTeams.viewTeams,
      members: members.map((m) => ({
        employeeId: m.employeeId,
        name: m.name,
        isManager: m.isManager,
        isAdmin: m.isAdmin,
        managerLevel: m.managerLevel,
        roleLabel: m.roleLabel,
      })),
      managers,
      shiftPlanning,
    };
  }
}
