import { drizzle, type MySql2Database } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import {
  existsAdmin,
  admin,
  type ExistsAdminReq,
  type GetAdminReq,
  getAdmins,
  type AddAdminReq,
  addAdmin,
  type EditAdminReq,
  editAdmin,
  type DeleteAdminReq,
  deleteAdmin
} from './schema/admin';
import {
  addTeam,
  deleteTeam,
  getTeams,
  getTeamById,
  getTeamByName,
  type AddTeamReq,
  type DeleteTeamReq,
  type GetTeamByIdReq,
  type GetTeamByNameReq,
  type GetTeamsReq,
  team,
  type GetTeamByUserUuidReq,
  getTeamByUserUuid,
  type EditTeamReq,
  editTeam,
  type GetTeamsFullReq,
  getTeamsFull
} from './schema/team';
import {
  addTeamDraft,
  deleteTeamDraft,
  getTeamDraftByMemberOne,
  type AddTeamDraftReq,
  type DeleteTeamDraftReq,
  type GetTeamDraftByMemberOneReq,
  teamDraft,
  type GetTeamDraftByUsernameReq,
  getTeamDraftByUsername
} from './schema/teamDraft';
import {
  addTeamMember,
  type AddTeamMemberReq,
  deleteTeamMemberByTeamId,
  type DeleteTeamMemberByTeamIdReq,
  teamMember
} from './schema/teamMember';
import {
  type AddUserReq,
  type EditUserReq,
  type DeleteUserReq,
  type GetUsersReq,
  type GetUserByUsernameReq,
  user,
  addUser,
  editUser,
  deleteUser,
  getUsers,
  getUserByUsername,
  existsUser,
  type ExistsUserReq,
  type GetUsersByUuidReq,
  getUsersByUuid,
  type GetUserByIdReq,
  getUserById
} from './schema/user';
import {
  type GetSettingReq,
  settings,
  getSetting,
  type GetSettingsReq,
  getSettings,
  type SetSettingsReq,
  setSettings
} from './schema/settings';
import {
  addDeath,
  type AddDeathReq,
  death,
  getDeathByUserId,
  type GetDeathByUserIdReq,
  getDeaths,
  type GetDeathsReq
} from './schema/death.ts';
import {
  addFeedback,
  type AddFeedbackReq,
  addUserFeedbacks,
  type AddUserFeedbacksReq,
  feedback,
  getFeedbacks,
  type GetFeedbacksReq
} from './schema/feedback.ts';
import { addReport, type AddReportReq, getReports, type GetReportsReq, report } from './schema/report.ts';
import { DATABASE_URI } from 'astro:env/server';
import { type GetStrikeReasonsReq, getStrikeReasons, strikeReason } from '@db/schema/strikeReason.ts';
import { getStrikesByTeamId, type GetStrikesByTeamId, strike } from '@db/schema/strike.ts';
import {
  editReportStatus,
  type EditReportStatusReq,
  getReportStatus,
  type GetReportStatusReq,
  reportStatus
} from '@db/schema/reportStatus.ts';
import {
  addBlockedUser,
  type AddBlockedUserReq,
  getBlockedUsers,
  type GetBlockedUsersReq,
  blockedUser,
  type GetBlockedUserByUuidReq,
  getBlockedUserByUuid,
  type EditBlockedUserReq,
  editBlockedUser,
  type DeleteBlockedUserReq,
  deleteBlockedUser
} from '@db/schema/blockedUser.ts';

export class Database {
  protected readonly db: MySql2Database<{
    admin: typeof admin;
    team: typeof team;
    teamDraft: typeof teamDraft;
    teamMember: typeof teamMember;
    user: typeof user;
    blockedUser: typeof blockedUser;
    death: typeof death;
    report: typeof report;
    reportStatus: typeof reportStatus;
    strike: typeof strike;
    strikeReason: typeof strikeReason;
    feedback: typeof feedback;
    settings: typeof settings;
  }>;

  private constructor(db: typeof this.db) {
    this.db = db;
  }

  static async init(databaseUri: string) {
    const connectionPool = mysql.createPool({
      uri: databaseUri
    });

    const db = drizzle({
      client: connectionPool,
      schema: {
        admin,
        team,
        teamDraft,
        teamMember,
        user,
        blockedUser,
        death,
        report,
        reportStatus,
        strike,
        strikeReason,
        feedback,
        settings
      },
      mode: 'default'
    });

    return new Database(db);
  }

  async transaction<T>(fn: (tx: Database & { rollback: () => never }) => Promise<T>): Promise<T> {
    return this.db.transaction((tx) => fn(new Database(tx) as Database & { rollback: () => never }));
  }

  /* admins */
  addAdmin = (values: AddAdminReq) => addAdmin(this.db, values);
  editAdmin = (values: EditAdminReq) => editAdmin(this.db, values);
  deleteAdmin = (values: DeleteAdminReq) => deleteAdmin(this.db, values);
  getAdmins = (values: GetAdminReq) => getAdmins(this.db, values);
  existsAdmin = (values: ExistsAdminReq) => existsAdmin(this.db, values);

  /* user */
  addUser = (values: AddUserReq) => addUser(this.db, values);
  editUser = (values: EditUserReq) => editUser(this.db, values);
  deleteUser = (values: DeleteUserReq) => deleteUser(this.db, values);
  existsUser = (values: ExistsUserReq) => existsUser(this.db, values);
  getUsers = (values: GetUsersReq) => getUsers(this.db, values);
  getUserById = (values: GetUserByIdReq) => getUserById(this.db, values);
  getUserByUsername = (values: GetUserByUsernameReq) => getUserByUsername(this.db, values);
  getUsersByUuid = (values: GetUsersByUuidReq) => getUsersByUuid(this.db, values);

  /* user blocks */
  addBlockedUser = (values: AddBlockedUserReq) => addBlockedUser(this.db, values);
  editBlockedUser = (values: EditBlockedUserReq) => editBlockedUser(this.db, values);
  deleteBlockedUser = (values: DeleteBlockedUserReq) => deleteBlockedUser(this.db, values);
  getBlockedUserByUuid = (values: GetBlockedUserByUuidReq) => getBlockedUserByUuid(this.db, values);
  getBlockedUsers = (values: GetBlockedUsersReq) => getBlockedUsers(this.db, values);

  /* team */
  addTeam = (values: AddTeamReq) => addTeam(this.db, values);
  editTeam = (values: EditTeamReq) => editTeam(this.db, values);
  deleteTeam = (values: DeleteTeamReq) => deleteTeam(this.db, values);
  getTeams = (values: GetTeamsReq) => getTeams(this.db, values);
  getTeamsFull = (values: GetTeamsFullReq) => getTeamsFull(this.db, values);
  getTeamById = (values: GetTeamByIdReq) => getTeamById(this.db, values);
  getTeamByName = (values: GetTeamByNameReq) => getTeamByName(this.db, values);
  getTeamByUserUuid = (values: GetTeamByUserUuidReq) => getTeamByUserUuid(this.db, values);

  /* team draft */
  addTeamDraft = (values: AddTeamDraftReq) => addTeamDraft(this.db, values);
  deleteTeamDraft = (values: DeleteTeamDraftReq) => deleteTeamDraft(this.db, values);
  getTeamDraftByUsername = (values: GetTeamDraftByUsernameReq) => getTeamDraftByUsername(this.db, values);
  getTeamDraftByMemberOne = (values: GetTeamDraftByMemberOneReq) => getTeamDraftByMemberOne(this.db, values);

  /* team member */
  addTeamMember = (values: AddTeamMemberReq) => addTeamMember(this.db, values);
  deleteTeamMemberByTeamId = (values: DeleteTeamMemberByTeamIdReq) => deleteTeamMemberByTeamId(this.db, values);

  /* death */
  addDeath = (values: AddDeathReq) => addDeath(this.db, values);
  getDeathByUserId = (values: GetDeathByUserIdReq) => getDeathByUserId(this.db, values);
  getDeaths = (values: GetDeathsReq) => getDeaths(this.db, values);

  /* report */
  addReport = (values: AddReportReq) => addReport(this.db, values);
  getReports = (values: GetReportsReq) => getReports(this.db, values);

  /* report status */
  getReportStatus = (values: GetReportStatusReq) => getReportStatus(this.db, values);
  editReportStatus = (values: EditReportStatusReq) => editReportStatus(this.db, values);

  /* strike reason */
  getStrikeReasons = (values: GetStrikeReasonsReq) => getStrikeReasons(this.db, values);
  getStrikesByTeamId = (values: GetStrikesByTeamId) => getStrikesByTeamId(this.db, values);

  /* feedback */
  addFeedback = (values: AddFeedbackReq) => addFeedback(this.db, values);
  addUserFeedbacks = (values: AddUserFeedbacksReq) => addUserFeedbacks(this.db, values);
  getFeedbacks = (values: GetFeedbacksReq) => getFeedbacks(this.db, values);

  /* settings */
  getSettings = (values: GetSettingsReq) => getSettings(this.db, values);
  setSettings = (values: SetSettingsReq) => setSettings(this.db, values);
  getSetting = (values: GetSettingReq) => getSetting(this.db, values);
}

export const db = await Database.init(DATABASE_URI);