import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { Crime } from "../Crime/Crime";
import { newWorkStats, scaleWorkStats, WorkStats, multWorkStats } from "./WorkStats";
import { Person as IPerson } from "@nsdefs";
import { CONSTANTS } from "../Constants";
import { ClassType, FactionWorkType, LocationName } from "@enums";
import {
  getFactionFieldWorkRepGain,
  getFactionSecurityWorkRepGain,
  getHackingWorkRepGain,
} from "../PersonObjects/formulas/reputation";
import { Locations } from "../Locations/Locations";
import { Location } from "../Locations/Location";
import { Player } from "@player";
import { Class, Classes } from "./ClassWork";
import { Server } from "../Server/Server";
import { GetServer } from "../Server/AllServers";
import { serverMetadata } from "../Server/data/servers";
import { Company } from "../Company/Company";
import { CompanyPosition } from "../Company/CompanyPosition";
import { isMember } from "../utils/EnumHelper";
import { getMultiplierFromCharisma } from "../DarkNet/effects/effects";

export function processWorkStats(person: IPerson, workStats: WorkStats): WorkStats {
  // "person" can be a normal object that the player passes to NS APIs, so we cannot use `person instanceof Sleeve`.
  if (Player.bitNodeOptions.disableSleeveExpAndAugmentation && "shock" in person) {
    workStats.hackExp = 0;
    workStats.strExp = 0;
    workStats.defExp = 0;
    workStats.dexExp = 0;
    workStats.agiExp = 0;
    workStats.chaExp = 0;
    workStats.intExp = 0;
  }
  return workStats;
}

const gameCPS = 1000 / CONSTANTS.MilliPerCycle; // 5 cycles per second
export const FactionWorkStats: Record<FactionWorkType, WorkStats> = {
  [FactionWorkType.hacking]: newWorkStats({ hackExp: 2 }),
  [FactionWorkType.field]: newWorkStats({
    hackExp: 1,
    strExp: 1,
    defExp: 1,
    dexExp: 1,
    agiExp: 1,
    chaExp: 1,
  }),
  [FactionWorkType.security]: newWorkStats({
    hackExp: 0.5,
    strExp: 1.5,
    defExp: 1.5,
    dexExp: 1.5,
    agiExp: 1.5,
  }),
};

export function calculateCrimeWorkStats(person: IPerson, crime: Crime): WorkStats {
  const gains = scaleWorkStats(
    multWorkStats(
      //Todo: rework crime and workstats interfaces to use the same naming convention for exp values, then we can just make a workStats directly from a crime.
      newWorkStats({
        money: crime.money,
        hackExp: crime.hacking_exp,
        strExp: crime.strength_exp,
        defExp: crime.defense_exp,
        dexExp: crime.dexterity_exp,
        agiExp: crime.agility_exp,
        chaExp: crime.charisma_exp,
        intExp: crime.intelligence_exp,
      }),
      person.mults,
      person.mults.crime_money * currentNodeMults.CrimeMoney,
    ),
    currentNodeMults.CrimeExpGain,
    false,
  );
  return processWorkStats(person, gains);
}

/** @returns faction rep rate per cycle */
export const calculateFactionRep = (person: IPerson, type: FactionWorkType, favor: number): number => {
  const repFormulas = {
    [FactionWorkType.hacking]: getHackingWorkRepGain,
    [FactionWorkType.field]: getFactionFieldWorkRepGain,
    [FactionWorkType.security]: getFactionSecurityWorkRepGain,
  };
  return repFormulas[type](person, favor);
};

/** @returns per-cycle WorkStats */
export function calculateFactionExp(person: IPerson, type: FactionWorkType): WorkStats {
  return processWorkStats(
    person,
    scaleWorkStats(multWorkStats(FactionWorkStats[type], person.mults), currentNodeMults.FactionWorkExpGain / gameCPS),
  );
}

/** Calculate cost for a class */
export function calculateCost(classs: Class, location: Location): number {
  const serverMeta = serverMetadata.find((s) => s.specialName === location.name);
  const server = GetServer(serverMeta ? serverMeta.hostname : "");
  const discount = (server as Server)?.backdoorInstalled ? 0.9 : 1;
  return classs.earnings.money * location.costMult * discount;
}

/** @returns per-cycle WorkStats */
export function calculateClassEarnings(person: IPerson, type: ClassType, locationName: LocationName): WorkStats {
  const hashManager = Player.hashManager;
  const classs = Classes[type];
  const location = Locations[locationName];

  const hashMult = isMember("GymType", type) ? hashManager.getTrainingMult() : hashManager.getStudyMult();

  const earnings = multWorkStats(
    scaleWorkStats(classs.earnings, (location.expMult / gameCPS) * hashMult, false),
    person.mults,
  );
  earnings.money = calculateCost(classs, location) / gameCPS;
  return processWorkStats(person, earnings);
}

/** @returns per-cycle WorkStats */
export const calculateCompanyWorkStats = (
  worker: IPerson,
  company: Company,
  companyPosition: CompanyPosition,
  favor: number,
): WorkStats => {
  // If player has SF-11, calculate salary multiplier from favor
  const favorMult = isNaN(favor) ? 1 : 1 + favor / 100;
  const bn11Mult = Player.activeSourceFileLvl(11) > 0 ? favorMult : 1;
  const sf15Mult = Player.activeSourceFileLvl(15) > 1 ? getMultiplierFromCharisma(1.5) : 1;

  const gains = scaleWorkStats(
    multWorkStats(
      {
        money: companyPosition.baseSalary * company.salaryMultiplier * bn11Mult * currentNodeMults.CompanyWorkMoney,
        hackExp: companyPosition.hackingExpGain,
        strExp: companyPosition.strengthExpGain,
        defExp: companyPosition.defenseExpGain,
        dexExp: companyPosition.dexterityExpGain,
        agiExp: companyPosition.agilityExpGain,
        chaExp: companyPosition.charismaExpGain,
      },
      worker.mults,
      worker.mults.work_money * sf15Mult,
      sf15Mult,
    ),
    company.expMultiplier * currentNodeMults.CompanyWorkExpGain,
    false,
  );

  const jobPerformance = companyPosition.calculateJobPerformance(worker);

  gains.reputation = jobPerformance * worker.mults.company_rep * favorMult * currentNodeMults.CompanyWorkRepGain;

  return processWorkStats(worker, gains);
};
