import { IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { ChildEntity, Column, Index, ManyToOne, OneToMany } from "typeorm";
import { ResponseRunner } from '../responses/ResponseRunner';
import type { DistanceDonation } from "./DistanceDonation";
import { Participant } from "./Participant";
import type { RunnerCard } from "./RunnerCard";
import { RunnerGroup } from "./RunnerGroup";
import type { Scan } from "./Scan";

/**
 * Defines the runner entity.
 * Runners differ from participants in being able to actually accumulate a ran distance through scans.
 * Runner's get organized in groups.
 */
@ChildEntity()
@Index(['group'])
export class Runner extends Participant {
  /**
   * The runner's associated group.
   * Can be a runner team or organization.
   */
  @IsNotEmpty()
  @ManyToOne(() => RunnerGroup, group => group.runners)
  group: RunnerGroup;

  /**
   * The runner's associated distanceDonations.
   * Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran.
   */
  @OneToMany(() => require("./DistanceDonation").DistanceDonation, (distanceDonation: DistanceDonation) => distanceDonation.runner, { nullable: true })
  distanceDonations!: DistanceDonation[];

  /**
   * The runner's associated cards.
   * Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past.
   */
  @OneToMany(() => require("./RunnerCard").RunnerCard, (card: RunnerCard) => card.runner, { nullable: true })
  cards!: RunnerCard[];

  /**
   * The runner's associated scans.
   * Used to link runners to scans (valid and fraudulant).
   */
  @OneToMany(() => require("./Scan").Scan, (scan: Scan) => scan.runner, { nullable: true })
  scans!: Scan[];

  /**
  * The last time the runner requested a selfservice link.
  * Used to prevent spamming of the selfservice link forgotten route.
  */
  @Column({ nullable: true, unique: false })
  @IsString()
  @IsOptional()
  resetRequestedTimestamp?: number;

  /**
   * Returns all valid scans associated with this runner.
   * This is implemented here to avoid duplicate code in other files.
   */
  public get validScans(): Scan[] {
    if (this.scans) {
      return this.scans.filter(scan => scan.valid == true);
    }
    return []
  }

  /**
   * Returns the total distance ran by this runner based on all his valid scans.
   * This is implemented here to avoid duplicate code in other files.
  */
  @IsInt()
  public get distance(): number {
    return this.validScans.reduce((sum, current) => sum + current.distance, 0);
  }

  /**
   * Returns the total donations a runner has collected based on his linked donations and distance ran.
  */
  @IsInt()
  public get distanceDonationAmount(): number {
    return this.distanceDonations.reduce((sum, current) => sum + current.amount, 0);
  }

  /**
   * Turns this entity into it's response class.
   */
  public toResponse(): ResponseRunner {
    return new ResponseRunner(this, true);
  }
}