import { Column, Entity, getRepository, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import { InventoryEntity } from './inventory.entity';
import { FactionEntity } from './faction.entity';
import { PhoneEntity } from './phone.entity';
import { CompanyEntity } from './company.entity';
import { CompanyPositionEntity } from './company-position.entity';
import { SessionEntity } from './session.entity';
import { FactionRankEntity } from './factionrank.entity';
import { PlayerSettings } from '../features/settings';
import { DriversLicenseEntity } from './drivers-license.entity';
import { WeaponLicenseEntity } from './weapon-license.entity';
import dayjs from 'dayjs';
import { BankAccountEntity } from './bank-account.entity';
import { PropertyKeyEntity } from './property-key.entity';
import { VehicleOrderEntity } from './vehicle-order.entity';
import { VehicleEntity } from './vehicle.entity';
import { CommercialDriversLicenseEntity } from './commercial-drivers-license.entity';
import { TaxiLicenseEntity } from './taxi-license.entity';
import { PrisonCellEntity } from './prison-cell.entity';
import { CharacterOutfitEntity, CharacterOutfitStack, CharacterOutfitStackState } from './character-outfit.entity';
import { CharacterTattooEntity } from './character-tattoo.entity';
import { SceneEntity } from './scene.entity';
import { GraffitiEntity } from './graffiti.entity';
import { PetEntity } from './pet.entity';
import { WeaponAttachmentEntity } from './weapon-attachment.entity';
import { colourableChats } from '../features/chat/const/chat-color-channels';
import { CharacterOverlay } from './character-overlay-preset.entity';
import { CharacterAppearanceEntity } from './character-appearance.entity';
import { aconfigSettings } from '../features/admin/types';
import { Gender } from '../../shared/const/gender.const';

@Entity('characters')
export class CharacterEntity {
    @PrimaryGeneratedColumn() id: number;
    @Column({ type: 'varchar', length: 24 }) name: string;
    @Column({ default: 500 }) cash: number;
    @Column({ type: 'varchar', width: 10, default: '2003-12-01' }) date_of_birth: string;
    @Column() gender: Gender;
    @Column({ type: 'int', default: null, nullable: true }) faction_id: number | null;
    @Column({ type: 'int', default: null, nullable: true }) faction_rank: number | null;
    @Column() account_id: number;
    @Column() level: number;
    @Column() play_time: number;
    @Column({ type: 'json', default: '{}' }) joined_job_play_times: { [companyId: number]: number };
    @Column('datetime') created: Date;
    @Column({ type: 'datetime', nullable: true }) last_online: Date | null;
    @Column({ type: 'int', nullable: true }) jail_time: number | null;
    @Column({ type: 'varchar', length: 32 }) spawn: string | null;
    @Column({ type: 'int', nullable: true }) ssn: number | null;
    @Column({ type: 'json' }) settings: Partial<PlayerSettings>;
    @Column({ type: 'varchar', length: 40, nullable: true, default: null }) walkstyle: string | null;
    @Column({ type: 'varchar', length: 30, default: 'mood_normal_1' }) expression: string;
    @Column({ type: 'int', nullable: true }) prison_cell_id: number | null;
    @Column({ type: 'int', nullable: true }) prison_minutes: number | null;
    @Column({ type: 'int', nullable: true }) prison_seg_minutes: number | null;
    @Column({ type: 'varchar', length: 24, nullable: true, default: null }) signature: string | null;
    @Column({ type: 'varchar', length: 32, nullable: true, default: null }) signature_font: string | null;
    @Column() payday_duty_buffer: number;
    @Column() welfare_buffer: number;
    @Column({ type: 'tinyint', width: 1, default: 0 }) killed: boolean;
    @Column({ type: 'json' }) outfit: CharacterOutfitStack;
    @Column({ type: 'json' }) overlay: CharacterOverlay[] | null;
    @Column({ type: 'json' }) outfit_state: CharacterOutfitStackState;
    @Column({ type: 'int', default: 0 }) reward_stack: number;

    @Column({ type: 'float' }) last_x: number;
    @Column({ type: 'float' }) last_y: number;
    @Column({ type: 'float' }) last_z: number;
    @Column({ type: 'int' }) last_d: number;

    @Column({ type: 'int', default: 1 }) max_companies: number;
    @Column({ type: 'int', default: 0 }) join_rewards: number;

    @Column({ type: 'json', default: '[0,0,0]' }) hairs: [number, number, number];
    @Column({ type: 'datetime', nullable: true }) lockpick_reset: Date | null;
    @Column({ type: 'datetime', nullable: true }) weaponfile_reset: Date | null;
    @Column({ type: 'datetime', nullable: true }) surgeon_reset: Date | null;

    @Column({ type: 'json' }) chat_colours: { [colour in keyof typeof colourableChats]: string } | Record<string, never> | null;
    @Column({ type: 'json' }) admin_config: { [setting in keyof typeof aconfigSettings]: boolean | undefined };

    @Column({ type: 'int', nullable: true }) nail_style_id: number | null;
    @Column({ type: 'tinyint', width: 1, default: 0 }) tutorial: boolean;

    @ManyToOne(() => PrisonCellEntity, (prisonCell) => prisonCell.residents)
    @JoinColumn({ name: 'prison_cell_id' })
    prisonCell: PrisonCellEntity;

    // @ManyToOne(() => CompanyEntity, (company) => company.employees)
    // @JoinColumn({ name: 'company_id' })
    // company?: CompanyEntity;

    @ManyToMany(() => CompanyEntity, {
        eager: true,
    })
    @JoinTable({
        name: 'character_company_positions',
        joinColumn: { name: 'character_id' },
        inverseJoinColumn: { name: 'company_id' },
    })
    companies: CompanyEntity[];

    @ManyToMany(() => CompanyPositionEntity, {
        eager: true,
    })
    @JoinTable({
        name: 'character_company_positions',
        joinColumn: { name: 'character_id' },
        inverseJoinColumn: { name: 'company_position_id' },
    })
    companyPositions: CompanyPositionEntity[];

    @OneToMany(() => BankAccountEntity, (bankAccount) => bankAccount.character)
    bankAccounts: BankAccountEntity[];

    @OneToMany(() => SessionEntity, (session) => session.character)
    @JoinColumn({ name: 'id' })
    sessions: SessionEntity;

    @OneToMany(() => GraffitiEntity, (graffiti) => graffiti.creator_id)
    graffitis: GraffitiEntity[];

    @OneToMany(() => PetEntity, (pet) => pet.character_id)
    pets: PetEntity[];

    @OneToMany(() => VehicleEntity, (vehicle) => vehicle.character)
    vehicles: VehicleEntity[];

    @OneToMany(() => PhoneEntity, (phone) => phone.character)
    phones: PhoneEntity[];

    @OneToMany(() => SceneEntity, (scene) => scene.character)
    scenes: SceneEntity[];

    @OneToMany(() => WeaponAttachmentEntity, (attachment) => attachment.character_id)
    weaponAttachments: WeaponAttachmentEntity[];

    phone?: PhoneEntity;

    inventory?: InventoryEntity;

    @ManyToOne(() => FactionEntity, (faction) => faction.members)
    @JoinColumn({ name: 'faction_id' })
    faction?: FactionEntity;

    factionRank?: FactionRankEntity;

    @OneToMany(() => CompanyEntity, (company) => company.owner)
    ownedCompanies: CompanyEntity[];

    @OneToMany(() => PropertyKeyEntity, (pke) => pke.character)
    propertyKeys: PropertyKeyEntity[];

    @OneToMany(() => VehicleOrderEntity, (voe) => voe.character)
    vehicleOrders: VehicleOrderEntity[];

    @OneToMany(() => CharacterOutfitEntity, (coe) => coe.character)
    outfits: CharacterOutfitEntity[];

    @OneToMany(() => CharacterTattooEntity, (cte) => cte.character)
    tattoos: CharacterTattooEntity[];

    @OneToOne(() => CharacterAppearanceEntity)
    @JoinColumn({
        name: 'id',
        referencedColumnName: 'character_id',
    })
    appearanceData?: CharacterAppearanceEntity;

    masked: boolean;
    maskedName: string;

    driverLicense?: DriversLicenseEntity;
    weaponLicense?: WeaponLicenseEntity;

    bankAccount?: BankAccountEntity;

    cdl?: CommercialDriversLicenseEntity;

    taxiLicense?: TaxiLicenseEntity;

    attributes?: string;

    async save(): Promise<CharacterEntity> {
        return await getRepository(CharacterEntity).save(this);
    }

    getIcName(includeMaskId = true): string {
        if (includeMaskId && this.masked) {
            return this.maskedName;
        }

        return this.name.replace('_', ' ');
    }

    getFactionName(includeMaskId = false): string {
        const parts = [];
        if (this.factionRank) {
            parts.push(this.factionRank.name);
        }
        parts.push(this.getIcName(includeMaskId));
        return parts.join(' ');
    }

    getFirstName(): string {
        return this.name.split('_')[0];
    }

    getAge(): number {
        return dayjs().diff(dayjs(this.date_of_birth), 'years');
    }

    getLastName(withInitial = false): string {
        const parts = this.name.split('_');
        if (withInitial) {
            return `${parts[0][0]}. ${parts[1]}`;
        }
        return parts[1];
    }

    getName(): string {
        return this.getIcName(false);
    }

    getSSN(): string | undefined {
        if (!this.ssn) {
            return undefined;
        }

        const s = String(this.ssn);
        return [s.slice(0, 3), s.slice(3, 5), s.slice(5)].join('-');
    }

    getSignatureStyle() {
        return this.signature || 'full';
    }

    getSignatureFont() {
        return this.signature_font || 'Alex Brush';
    }

    getEntityType(): 'character' {
        return 'character';
    }

    getSignature() {
        const style = this.getSignatureStyle();

        if (style === 'full') {
            return this.name.replace('_', ' ');
        }

        const parts = this.name.split('_');

        if (style === 'firsti') {
            return `${parts[0][0]}. ${parts[1]}`;
        }

        if (style === 'firstin') {
            return `${parts[0][0]} ${parts[1]}`;
        }

        if (style === 'lasti') {
            return `${parts[0]} ${parts[1][0]}.`;
        }

        if (style === 'lastin') {
            return `${parts[0]} ${parts[1][0]}`;
        }

        if (style === 'i') {
            return `${parts[0][0]}. ${parts[1][0]}.`;
        }

        if (style === 'in') {
            return `${parts[0][0]} ${parts[1][0]}`;
        }

        if (style === 'last') {
            return `${parts[1]}`;
        }
    }
}
