import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, getRepository, OneToMany, ManyToOne } from 'typeorm';
import { CharacterEntity } from './character.entity';
import { FurnitureItemEntity } from './furniture-item.entity';
import { PropertySecurityEntity } from './property-security.entity';
import { CompanyEntity } from './company.entity';
import { PropertyStoreStockEntity } from './property-store-stock.entity';
import { PropertyAreaEntity } from './property-area.entity';
import { isEmpty } from 'lodash';
import { propertySystem } from '../features/properties';
import { DarknetOrderEntity } from './darknet-order.entity';
import { InteriorEntity } from './interior.entity';
import { FactionEntity } from './faction.entity';
import { RadioStreamingEntityData } from '../features/radio';
import { PropertyKeyEntity } from './property-key.entity';
import { PropertyPermitEntity } from './property-permit.entity';
import { WeatherName } from '../features/weather/weather.defs';
import { factionSystem } from '../features/faction';
import { companySystem } from '../features/company';
import { playerSystem } from '../features/player';
import { PropertyRenterEntity } from './property-renter.entity';
import { preloadSystem } from '../core/preload';
import { PaintballRuleSet } from '../features/faction/features/less-lethal';

export type OptionalPropertyData = {
    paintball?: PaintballRuleSet;
};

@Entity('properties')
export class PropertyEntity {
    @PrimaryGeneratedColumn() id: number;
    @Column({ type: 'varchar', length: 100, nullable: true, default: null }) name: string | null;
    @Column({ type: 'varchar', length: 30 }) type: string;
    @Column({ type: 'varchar', length: 64, nullable: true }) owner_name: string | null;
    @Column({ type: 'int', nullable: true, default: null }) parent_property_id: number | null;
    @Column({ type: 'int', nullable: true, default: null }) master_property_id: number | null;
    @Column({ type: 'int', nullable: true, default: null }) character_id: number | null;
    @Column({ type: 'int', nullable: true, default: null }) faction_id: number | null;
    @Column({ type: 'int', nullable: true, default: null }) company_id: number | null;
    @Column({ type: 'int', nullable: true }) enter_dimension: number | null;
    @Column({ type: 'int' }) enter_x: number;
    @Column({ type: 'int' }) enter_y: number;
    @Column({ type: 'int' }) enter_z: number;
    @Column({ type: 'int' }) enter_h: number;
    @Column({ type: 'int', nullable: true }) interior_dimension: number | null;
    @Column({ type: 'int', nullable: true }) interior_x: number | null;
    @Column({ type: 'int', nullable: true }) interior_y: number | null;
    @Column({ type: 'int', nullable: true }) interior_z: number | null;
    @Column({ type: 'int', nullable: true }) interior_h: number | null;
    @Column({ type: 'int', nullable: true }) interior_id: number | null;
    @Column({ type: 'varchar', length: 32, nullable: true }) interior_objects_tag: string | null;
    @Column({ type: 'int', default: 0 }) extra_furniture_slots: number;
    @Column({ type: 'int', default: 0 }) extra_inventory_slots: number;
    @Column({ type: 'tinyint', default: 0 }) is_primary: boolean;

    @Column({ type: 'json', default: null }) for_sale: { faction: number; company: number; character: number } | null;
    @Column({ type: 'json', default: null }) for_rent: { faction: number; company: number; character: number } | null;

    @Column({ type: 'int', nullable: true }) rent: number | null;
    @Column({ type: 'int', default: 0 }) rent_debt: number;
    @Column({ type: 'int', default: 0 }) rent_deposit: number;
    @Column({ type: 'datetime', nullable: true }) rent_start: Date | null;

    @Column({ type: 'boolean' }) locked: boolean;
    @Column({ type: 'varchar', length: 128 }) street_name: string;
    @Column({ type: 'varchar', length: 128 }) zone: string;

    @Column({ type: 'int', default: null, nullable: true }) static_time: number | null;
    @Column({ type: 'varchar', length: 32, default: null, nullable: true }) static_weather: WeatherName | null;

    @Column({ type: 'int', default: null, nullable: true }) blip_type: number | null;
    @Column({ type: 'varchar', length: 10, default: 'NEVER_SHOW' }) blip_frequency: PropertyBlipFrequency;
    @Column({ type: 'varchar', length: 40, default: null }) blip_label: string | null;
    @Column({ type: 'boolean' }) free_furniture: boolean;

    @Column({ type: 'int', nullable: true }) freeze: number | null;

    @Column({ type: 'int', nullable: true }) entrance_reward: number | null;
    @Column({ type: 'int', default: 8 }) entrance_reward_reset_hours: number;
    @Column({ type: 'int', default: 30 }) entrance_visitor_cap: number;
    @Column({ type: 'int', default: 8 }) entrance_visitor_cap_reset_hours: number;

    @Column({ type: 'int', nullable: true }) hospital_price: number | null;
    @Column({ type: 'tinyint', default: false }) money_launderer: boolean;
    @Column({ type: 'tinyint', default: false }) security: boolean;
    @Column({ type: 'tinyint', default: false }) protected: boolean;
    @Column({ type: 'datetime', nullable: true }) burglary_cooldown: Date | null;

    @Column({ type: 'json' }) data: OptionalPropertyData | null;

    @Column({ type: 'tinyint', default: false }) cut_power: boolean;

    areas: PropertyAreaEntity[] = [];

    @OneToMany(() => PropertyStoreStockEntity, (storeStock) => storeStock.property)
    @JoinColumn({ name: 'property_id' })
    itemStock: PropertyStoreStockEntity[];

    @OneToMany(() => FurnitureItemEntity, (item) => item.property)
    @JoinColumn({ name: 'property_id' })
    furnitureItems: FurnitureItemEntity[];

    @OneToMany(() => PropertySecurityEntity, (item) => item.property)
    @JoinColumn({ name: 'property_id' })
    securitySystem: PropertySecurityEntity[];

    @ManyToOne(() => CharacterEntity)
    @JoinColumn({
        name: 'character_id',
        referencedColumnName: 'id',
    })
    character?: CharacterEntity;

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

    @ManyToOne(() => InteriorEntity, (interior) => interior.properties)
    @JoinColumn({
        name: 'interior_id',
    })
    interior?: InteriorEntity;

    @ManyToOne(() => CompanyEntity)
    @JoinColumn({
        name: 'company_id',
        referencedColumnName: 'id',
    })
    company?: CompanyEntity;

    @OneToMany(() => DarknetOrderEntity, (order) => order.property)
    @JoinColumn({
        name: 'property_id',
    })
    darknetOrders: DarknetOrderEntity[];

    @OneToMany(() => PropertyPermitEntity, (permit) => permit.property)
    permits: PropertyPermitEntity[];

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

    // Complexes, separate perms
    parent?: PropertyEntity;
    children: PropertyEntity[] = [];

    // Garages and other extensions of property
    master?: PropertyEntity;
    slaves: PropertyEntity[] = [];

    entranceColShape?: ColshapeMp;
    outsideRadioColShape?: ColshapeMp;
    interiorColShapes: ColshapeMp[] = [];

    furnitureLoaded?: boolean;
    furnitureLoading?: boolean;
    furnitureUnload?: NodeJS.Timeout;

    furnitureSlots = 0;

    // Security
    securityLoaded?: boolean;
    securityLoading: boolean;

    radioStation?: RadioStreamingEntityData;

    businessOpen = false;

    lightsOn = true;

    blip?: BlipMp;

    interiorObjects?: {
        entities: ObjectMp[];
        interiorId: number;
        tag: string | null;
    };

    renters: (CharacterEntity | FactionEntity | CompanyEntity)[] = [];

    autoLowForced?: boolean;

    hasEntrancePoint(): boolean {
        return this.enter_x !== null;
    }

    hasInterior(): boolean {
        if (this.interior) {
            return true;
        }

        return !!this.interior_x;
    }

    enterPosition() {
        return new mp.Vector3(this.enter_x, this.enter_y, this.enter_z);
    }

    interiorPosition(): Vector3 | null {
        if (this.interior_x && this.interior_y && this.interior_z) {
            return new mp.Vector3(this.interior_x, this.interior_y, this.interior_z);
        } else if (this.interior) {
            return this.interior.position;
        }

        return null;
    }

    hasChildren(): boolean {
        return !isEmpty(this.children);
    }

    hasParent(): boolean {
        return !!this.parent;
    }

    canBeEnteredByVehicle(): boolean {
        return this.type === 'garage';
    }

    getName(): string {
        return this.name || `Property #${this.id}`;
    }

    isBusiness(): boolean {
        return this.type === 'business';
    }

    async getOwner(): Promise<string> {
        if (this.faction_id) {
            return factionSystem.getFactionFromId(this.faction_id)?.name || `Faction ${this.faction_id}`;
        }
        if (this.company_id) {
            return companySystem.getCompanyById(this.company_id)?.getName() || `Company ${this.company_id}`;
        }
        if (this.character_id) {
            return (await playerSystem.getCharacterFromId(this.character_id))?.getIcName(false) || `Character ${this.character_id}`;
        }
        return `Unknown`;
    }

    async getOwnerEntity(with_details = false): Promise<CharacterEntity | CompanyEntity | FactionEntity | undefined> {
        if (this.faction_id) {
            return factionSystem.getFactionFromId(this.faction_id);
        }
        if (this.company_id) {
            return companySystem.getCompanyById(this.company_id);
        }
        if (this.character_id) {
            return with_details ? await playerSystem.getCharacterFromIdWithBP(this.character_id) : await playerSystem.getCharacterFromId(this.character_id);
        }
        return undefined;
    }

    getOwnerId(): number {
        return this.faction_id || this.company_id || this.character_id || 0;
    }

    async getRentStart(entity: CharacterEntity | CompanyEntity | FactionEntity): Promise<Date | undefined> {
        const renterType = entity instanceof CharacterEntity ? 'character' : entity instanceof FactionEntity ? 'faction' : 'company';
        const renterTypeId = preloadSystem.getGameEntityTypeId(renterType);
        const renterEntity = await getRepository(PropertyRenterEntity).findOne({ entity_type_id: renterTypeId, entity_id: entity.id, property_id: this.id });
        return renterEntity?.rent_start;
    }

    getFullAddress(includeCity = false, includeState = false): string {
        let suffix = '';

        if (this.type === 'business') {
            suffix = '-B';
        } else if (this.type === 'garage') {
            suffix = '-G';
            if (this.master) {
                return `${this.master.id}${suffix} ${this.master.street_name}, ${this.master.zone}`;
            }
        } else if (this.parent_property_id) {
            suffix = '-A';

            return `${this.parent_property_id} ${this.street_name}, ${this.zone} Apt #${propertySystem.getApartmentNumber(this)}`;
        }

        let address = `${this.id}${suffix} ${this.street_name}, ${this.zone}`;

        if (includeCity) {
            address += ', Blaine County';
        }

        if (includeState) {
            address += ', San Andreas';
        }

        return address;
    }

    rentNegotiation?: PlayerMp;
}

export type PropertyBlipFrequency = 'NEVER_SHOW' | 'ON_BIZ_OPEN' | 'ALWAYS_SHOW';

export const PropertyTypes = ['house', 'apartment', 'complex', 'business', 'garage', 'trailer', 'hospital', 'bank', 'surgeon'];
export const PropertyAreaTypes = [
    'gas',
    'carwash',
    'store',
    'general',
    'pharmacy',
    'clothing',
    'mechanic',
    'hospital',
    'outfit',
    'bank',
    'fishing',
    'tattoo',
    'occluded',
];
