import { alias, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core';
import type { MySql2Database } from 'drizzle-orm/mysql2';
import { and, desc, eq, isNotNull, like } from 'drizzle-orm';
import { reportStatus } from './reportStatus.ts';
import { generateRandomString } from '@util/random.ts';
import { BASE_PATH } from 'astro:env/server';
import { strikeReason } from '@db/schema/strikeReason.ts';
import { strike } from '@db/schema/strike.ts';
import { user } from '@db/schema/user.ts';

type Database = MySql2Database<{ report: typeof report }>;

export const report = mysqlTable('report', {
  id: int('id').primaryKey().autoincrement(),
  reason: varchar('reason', { length: 255 }).notNull(),
  body: text('body'),
  urlHash: varchar('url_hash', { length: 255 }).notNull(),
  createdAt: timestamp('created_at', { mode: 'date' }),
  reporterId: int('reporter_id').references(() => user.id),
  reportedId: int('reported_id').references(() => user.id)
});

export type AddReportReq = {
  reason: string;
  body: string | null;
  createdAt?: Date | null;
  reporterId?: number;
  reportedId?: number | null;
};

export type EditReportReq = {
  id: number;
  reportedId?: number | null;
  createdAt?: Date | null;
};

export type SubmitReportReq = {
  urlHash: string;
  reportedId: number | null;
  reason: string;
  body: string;
};

export type GetReportsReq = {
  reporter?: string | null;
  reported?: string | null;
  includeDrafts?: boolean | null;
};

export type GetReportById = {
  id: number;
};

export type GetReportByUrlHash = {
  urlHash: string;
};

export async function addReport(db: Database, values: AddReportReq) {
  const urlHash = generateRandomString(16);

  const r = await db
    .insert(report)
    .values({
      reason: values.reason,
      body: values.body,
      urlHash: urlHash,
      createdAt: values.createdAt,
      reporterId: values.reporterId,
      reportedId: values.reportedId
    })
    .$returningId();

  return Object.assign(r[0], { url: `${BASE_PATH}/report/${urlHash}` });
}

export async function editReport(db: Database, values: EditReportReq) {
  return db
    .update(report)
    .set({
      reportedId: values.reportedId,
      createdAt: values.createdAt ?? undefined
    })
    .where(eq(report.id, values.id));
}

export async function submitReport(db: Database, values: SubmitReportReq) {
  return db
    .update(report)
    .set({
      reportedId: values.reportedId,
      reason: values.reason,
      body: values.body,
      createdAt: new Date()
    })
    .where(eq(report.urlHash, values.urlHash));
}

export async function getReports(db: Database, values: GetReportsReq) {
  const reporter = alias(user, 'reporter');
  const reported = alias(user, 'reported');

  return db
    .select({
      id: report.id,
      reason: report.reason,
      body: report.body,
      urlHash: report.urlHash,
      createdAt: report.createdAt,
      reporter: {
        id: reporter.id,
        username: reporter.username,
        uuid: reporter.uuid
      },
      reported: {
        id: reported.id,
        username: reported.username,
        uuid: reported.uuid
      },
      status: {
        status: reportStatus.status,
        notice: reportStatus.notice,
        statement: reportStatus.statement
      },
      strike: {
        strikeReasonId: strikeReason.id
      }
    })
    .from(report)
    .innerJoin(reporter, eq(report.reporterId, reporter.id))
    .leftJoin(reported, eq(report.reportedId, reported.id))
    .leftJoin(reportStatus, eq(report.id, reportStatus.reportId))
    .leftJoin(strike, eq(report.id, strike.reportId))
    .leftJoin(strikeReason, eq(strike.strikeReasonId, strikeReason.id))
    .where(
      and(
        values.reporter != null ? like(reporter.username, `%${values.reporter}%`) : undefined,
        values.reported != null ? like(reported.username, `%${values.reported}%`) : undefined,
        !values.includeDrafts ? isNotNull(report.createdAt) : undefined
      )
    )
    .orderBy(desc(report.id));
}

export async function getReportById(db: Database, values: GetReportById) {
  const reporter = alias(user, 'reporter');
  const reported = alias(user, 'reported');

  const reports = await db
    .select({
      id: report.id,
      reason: report.reason,
      body: report.body,
      createdAt: report.createdAt,
      reporter: {
        id: reporter.id,
        username: reporter.username
      },
      reported: {
        id: reported.id,
        username: reported.username
      },
      status: {
        status: reportStatus.status,
        notice: reportStatus.notice,
        statement: reportStatus.statement
      }
    })
    .from(report)
    .innerJoin(reporter, eq(report.reporterId, reporter.id))
    .leftJoin(reported, eq(report.reportedId, reported.id))
    .leftJoin(reportStatus, eq(report.id, reportStatus.reportId))
    .where(eq(report.id, values.id));

  return reports[0] ?? null;
}

export async function getReportByUrlHash(db: Database, values: GetReportByUrlHash) {
  const reporter = alias(user, 'reporter');
  const reported = alias(user, 'reported');

  const reports = await db
    .select({
      id: report.id,
      reason: report.reason,
      body: report.body,
      createdAt: report.createdAt,
      urlHash: report.urlHash,
      reporter: {
        id: reporter.id,
        username: reporter.username,
        uuid: reporter.uuid
      },
      reported: {
        id: reported.id,
        username: reported.username,
        uuid: reported.uuid
      },
      status: {
        status: reportStatus.status,
        notice: reportStatus.notice,
        statement: reportStatus.statement
      }
    })
    .from(report)
    .innerJoin(reporter, eq(report.reporterId, reporter.id))
    .leftJoin(reported, eq(report.reportedId, reported.id))
    .leftJoin(reportStatus, eq(report.id, reportStatus.reportId))
    .where(eq(report.urlHash, values.urlHash));

  return reports[0] ?? null;
}
