import type { AstroCookies, AstroCookieSetOptions } from 'astro';
import { ActionError } from 'astro:actions';
import crypto from 'node:crypto';
import { Permissions } from './permissions.ts';
import { ADMIN_COOKIE } from 'astro:env/server';

export class Session {
  static readonly #cookieOptions: AstroCookieSetOptions = {
    httpOnly: true,
    path: '/',
    sameSite: 'lax'
  };
  static #sessions: Session[] = [];

  readonly sessionId: string;
  readonly adminId: number;
  readonly permissions: Permissions;

  private constructor(sessionId: string, adminId: number, permissions: Permissions) {
    this.sessionId = sessionId;
    this.adminId = adminId;
    this.permissions = permissions;

    Session.#sessions.push(this);
  }

  invalidate(cookies?: AstroCookies) {
    for (let i = 0; i < Session.#sessions.length; i++) {
      if (Session.#sessions[i] == this) {
        Session.#sessions = Session.#sessions.splice(i, 1);
        if (cookies) cookies.delete(ADMIN_COOKIE, Session.#cookieOptions);
        break;
      }
    }
  }

  static newSession(adminId: number, permissions: Permissions, cookies: AstroCookies) {
    const session = new Session(crypto.randomBytes(16).toString('hex'), adminId, permissions);
    Session.#sessions.push(session);

    cookies.set(ADMIN_COOKIE, session.sessionId, Session.#cookieOptions);

    return session;
  }

  static sessionFromCookies(cookies: AstroCookies, neededPermissions?: Permissions) {
    const sessionId = cookies.get(ADMIN_COOKIE);
    if (!sessionId) return null;

    for (const session of Session.#sessions) {
      if (session.sessionId == sessionId.value) {
        if (neededPermissions && !session.permissions.hasPermissions(neededPermissions)) {
          break;
        }
        return session;
      }
    }

    return null;
  }

  static actionSessionFromCookies(cookies: AstroCookies, neededPermissions?: Permissions) {
    const sessionId = cookies.get(ADMIN_COOKIE);
    if (!sessionId) throw new ActionError({ code: 'UNAUTHORIZED' });

    for (const session of Session.#sessions) {
      if (session.sessionId == sessionId.value) {
        if (neededPermissions && !session.permissions.hasPermissions(neededPermissions)) {
          throw new ActionError({ code: 'UNAUTHORIZED' });
        }
        return session;
      }
    }

    throw new ActionError({ code: 'UNAUTHORIZED' });
  }
}
