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 {
  type AddUserReq,
  type EditUserReq,
  type DeleteUserReq,
  type GetUsersReq,
  type GetUserByUsernameReq,
  user,
  addUser,
  editUser,
  deleteUser,
  getUsers,
  getUserByUsername,
  existsUser,
  type ExistsUserReq,
  type GetUserByUuidReq,
  getUserByUuid,
  type GetUserByIdReq,
  getUserById
} from './schema/user';
import {
  type GetSettingReq,
  settings,
  getSetting,
  type GetSettingsReq,
  getSettings,
  type SetSettingsReq,
  setSettings
} from './schema/settings';
import {
  addFeedback,
  type AddFeedbackReq,
  addUserFeedbacks,
  type AddUserFeedbacksReq,
  feedback,
  getFeedbackByUrlHash,
  type GetFeedbackByUrlHash,
  getFeedbacks,
  type GetFeedbacksReq,
  submitFeedback,
  type SubmitFeedbackReq
} from './schema/feedback.ts';
import {
  addReport,
  type AddReportReq,
  editReport,
  type EditReportReq,
  getReportById,
  type GetReportById,
  getReportByUrlHash,
  type GetReportByUrlHash,
  getReports,
  type GetReportsReq,
  report,
  submitReport,
  type SubmitReportReq
} from './schema/report.ts';
import { DATABASE_URI } from 'astro:env/server';
import {
  type GetStrikeReasonsReq,
  getStrikeReasons,
  strikeReason,
  type AddStrikeReasonReq,
  addStrikeReason,
  type EditStrikeReasonReq,
  editStrikeReason,
  type DeleteStrikeReasonReq,
  deleteStrikeReason
} from '@db/schema/strikeReason.ts';
import {
  deleteStrike,
  type DeleteStrikeReq,
  editStrike,
  type EditStrikeReq,
  getStrikeByReportId,
  type GetStrikeByReportIdReq,
  getStrikesByUserId,
  type GetStrikesByUserIdReq,
  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';
import {
  addReportAttachment,
  type AddReportAttachmentReq,
  getReportAttachments,
  type GetReportAttachmentsReq,
  reportAttachment
} from '@db/schema/reportAttachment.ts';
import {
  addDirectSignup,
  type AddDirectSignupReq,
  deleteDirectSignup,
  type DeleteDirectSignupReq,
  directSignup,
  getDirectSignupByHash,
  type GetDirectSignupByHashReq,
  getDirectSignups,
  type GetDirectSignupsReq,
  setDirectSignupUser,
  type SetDirectSignupUserReq
} from '@db/schema/directSignup.ts';

export class Database {
  protected readonly db: MySql2Database<{
    admin: typeof admin;
    user: typeof user;
    blockedUser: typeof blockedUser;
    directSignup: typeof directSignup;
    report: typeof report;
    reportAttachment: typeof reportAttachment;
    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,
        user,
        blockedUser,
        directSignup,
        report,
        reportAttachment,
        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);
  getUserByUuid = (values: GetUserByUuidReq) => getUserByUuid(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);

  /* direct signup */
  addDirectSignup = (values: AddDirectSignupReq) => addDirectSignup(this.db, values);
  setDirectSignupUser = (values: SetDirectSignupUserReq) => setDirectSignupUser(this.db, values);
  deleteDirectSignup = (values: DeleteDirectSignupReq) => deleteDirectSignup(this.db, values);
  getDirectSignups = (values: GetDirectSignupsReq) => getDirectSignups(this.db, values);
  getDirectSignupByHash = (values: GetDirectSignupByHashReq) => getDirectSignupByHash(this.db, values);

  /* report */
  addReport = (values: AddReportReq) => addReport(this.db, values);
  editReport = (values: EditReportReq) => editReport(this.db, values);
  submitReport = (values: SubmitReportReq) => submitReport(this.db, values);
  getReports = (values: GetReportsReq) => getReports(this.db, values);
  getReportById = (values: GetReportById) => getReportById(this.db, values);
  getReportByUrlHash = (values: GetReportByUrlHash) => getReportByUrlHash(this.db, values);

  /* report attachment */
  addReportAttachment = (values: AddReportAttachmentReq) => addReportAttachment(this.db, values);
  getReportAttachments = (values: GetReportAttachmentsReq) => getReportAttachments(this.db, values);

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

  /* strike reason */
  addStrikeReason = (values: AddStrikeReasonReq) => addStrikeReason(this.db, values);
  editStrikeReason = (values: EditStrikeReasonReq) => editStrikeReason(this.db, values);
  deleteStrikeReason = (values: DeleteStrikeReasonReq) => deleteStrikeReason(this.db, values);
  getStrikeReasons = (values: GetStrikeReasonsReq) => getStrikeReasons(this.db, values);

  /* strikes */
  editStrike = (values: EditStrikeReq) => editStrike(this.db, values);
  deleteStrike = (values: DeleteStrikeReq) => deleteStrike(this.db, values);
  getStrikeByReportId = (values: GetStrikeByReportIdReq) => getStrikeByReportId(this.db, values);
  getStrikesByUserId = (values: GetStrikesByUserIdReq) => getStrikesByUserId(this.db, values);

  /* feedback */
  addFeedback = (values: AddFeedbackReq) => addFeedback(this.db, values);
  addUserFeedbacks = (values: AddUserFeedbacksReq) => addUserFeedbacks(this.db, values);
  submitFeedback = (values: SubmitFeedbackReq) => submitFeedback(this.db, values);
  getFeedbacks = (values: GetFeedbacksReq) => getFeedbacks(this.db, values);
  getFeedbackByUrlHash = (values: GetFeedbackByUrlHash) => getFeedbackByUrlHash(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);
