import { DataTypes } from 'sequelize';
import { env } from '$env/dynamic/private';
import { building, dev } from '$app/environment';
import * as bcrypt from 'bcrypt';
import {
	BeforeCreate,
	BeforeUpdate,
	BelongsTo,
	Column,
	ForeignKey,
	Index,
	Model,
	Sequelize,
	Table
} from 'sequelize-typescript';
import { Permissions } from '$lib/permissions';

@Table({ modelName: 'user', underscored: true })
export class User extends Model {
	@Column({ type: DataTypes.STRING, allowNull: false })
	declare firstname: string;
	@Column({ type: DataTypes.STRING, allowNull: false })
	declare lastname: string;
	@Column({ type: DataTypes.DATE, allowNull: false })
	declare birthday: Date;
	@Column({ type: DataTypes.STRING })
	declare telephone: string;
	@Column({ type: DataTypes.STRING, allowNull: false })
	declare username: string;
	@Column({ type: DataTypes.ENUM('java', 'bedrock', 'noauth'), allowNull: false })
	declare playertype: 'java' | 'bedrock' | 'noauth';
	@Column({ type: DataTypes.STRING })
	declare password: string;
	@Column({ type: DataTypes.UUID, unique: true })
	@Index
	declare uuid: string;
}

@Table({ modelName: 'report', underscored: true })
export class Report extends Model {
	@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
	@Index
	declare url_hash: string;
	@Column({ type: DataTypes.STRING, allowNull: false })
	declare subject: string;
	@Column({ type: DataTypes.STRING })
	declare body: string;
	@Column({ type: DataTypes.BOOLEAN, allowNull: false })
	declare draft: boolean;
	@Column({ type: DataTypes.ENUM('none', 'review', 'reviewed'), allowNull: false })
	declare status: 'none' | 'review' | 'reviewed';
	@Column({ type: DataTypes.STRING })
	declare notice: string;
	@Column({ type: DataTypes.STRING })
	declare statement: string;
	@Column({ type: DataTypes.INTEGER, allowNull: false })
	@ForeignKey(() => User)
	declare reporter_id: number;
	@Column({ type: DataTypes.INTEGER })
	@ForeignKey(() => User)
	declare reported_id: number;
	@Column({ type: DataTypes.INTEGER })
	@ForeignKey(() => Admin)
	declare auditor_id: number;
	@Column({ type: DataTypes.INTEGER })
	@ForeignKey(() => StrikeReason)
	declare strike_reason_id: number | null;

	@BelongsTo(() => User, 'reporter_id')
	declare reporter: User;
	@BelongsTo(() => User, 'reported_id')
	declare reported: User;
	@BelongsTo(() => Admin, 'auditor_id')
	declare auditor: Admin;
	@BelongsTo(() => StrikeReason, 'strike_reason_id')
	declare strike_reason: StrikeReason;
}

@Table({ modelName: 'strike_reason', underscored: true })
export class StrikeReason extends Model {
	@Column({ type: DataTypes.INTEGER, allowNull: false })
	declare weight: number;
	@Column({ type: DataTypes.STRING, allowNull: false })
	declare name: string;
}

@Table({ modelName: 'strike_punishment', underscored: true })
export class StrikePunishment extends Model {
	@Column({ type: DataTypes.INTEGER, allowNull: false })
	declare weight: number;
	@Column({ type: DataTypes.INTEGER, allowNull: false })
	declare ban_in_seconds: number;
}

@Table({ modelName: 'strike', underscored: true })
export class Strike extends Model {
	@Column({ type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 })
	declare weight: number;
	@Column({ type: DataTypes.DATE, allowNull: false, defaultValue: 0 })
	declare ban_until: Date;
	@Column({ type: DataTypes.INTEGER, allowNull: false })
	@ForeignKey(() => User)
	declare user_id: number;

	@BelongsTo(() => User, 'user_id')
	declare user: User;
}

@Table({ modelName: 'admin', underscored: true })
export class Admin extends Model {
	@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
	declare username: string;
	@Column({ type: DataTypes.STRING, allowNull: false })
	declare password: string;
	@Column({
		type: DataTypes.BIGINT,
		allowNull: false,
		get(this: Admin): Permissions | null {
			const permissions = this.getDataValue('permissions');
			return permissions != null ? new Permissions(permissions) : null;
		},
		set(this: Admin, value: Permissions) {
			this.setDataValue('permissions', value.value);
		}
	})
	declare permissions: Permissions;

	@BeforeCreate
	@BeforeUpdate
	static hashPassword(instance: Admin) {
		if ((instance.changed() || []).indexOf('password') != -1) {
			instance.password = bcrypt.hashSync(instance.password, 10);
		}
	}

	validatePassword(password: string): boolean {
		return bcrypt.compareSync(password, this.password);
	}
}

@Table({ modelName: 'settings', underscored: true })
export class Settings extends Model {
	@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
	declare key: string;
	@Column({
		type: DataTypes.STRING,
		allowNull: false,
		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		get(this: Settings): any {
			const value = this.getDataValue('value');
			return value != null ? JSON.parse(value) : null;
		}
	})
	declare value: string;
}

export const sequelize = new Sequelize(building ? 'sqlite::memory:' : env.DATABASE_URI, {
	// only log sql queries in dev mode
	logging: dev ? console.log : false,
	models: [User, Report, StrikeReason, StrikePunishment, Strike, Admin, Settings]
});