import type { NextFunction, Request, Response } from "express";
import { one } from "../db/client.js";
import { config } from "../config.js";
import { sha256 } from "../lib/crypto.js";
import { unauthorized } from "../lib/errors.js";

export interface SessionUser {
  id: number;
  username: string;
  fullName: string;
  email: string | null;
  mustChangePassword: boolean;
  isDemo: boolean;
  roles: { code: string; nameAr: string; requiresScope: boolean; governorateIds: number[] }[];
  permissions: Set<string>;
}

declare module "express-serve-static-core" {
  interface Request {
    user?: SessionUser;
    sessionId?: string;
  }
}

const SESSION_LOAD_SQL = `
  SELECT
    u.id, u.username, u.full_name, u.email, u.must_change_password, u.is_demo,
    s.id AS session_id,
    COALESCE(
      (SELECT json_agg(json_build_object(
        'code', r.code, 'nameAr', r.name_ar, 'requiresScope', r.requires_scope,
        'governorateIds', COALESCE((
          SELECT array_agg(urs.scope_id)
          FROM user_role_scopes urs WHERE urs.user_role_id = ur.id AND urs.scope_type = 'governorate'
        ), '{}')
      ) ORDER BY r.sort_order)
      FROM user_roles ur
      JOIN roles r ON r.id = ur.role_id AND r.is_enabled
      WHERE ur.user_id = u.id AND ur.revoked_at IS NULL
        AND ur.valid_from <= now() AND (ur.valid_until IS NULL OR ur.valid_until > now())),
      '[]'::json
    ) AS roles,
    COALESCE(
      (SELECT json_agg(DISTINCT p.code)
        FROM user_roles ur
        JOIN roles r ON r.id = ur.role_id AND r.is_enabled
        JOIN role_permissions rp ON rp.role_id = r.id
        JOIN permissions p ON p.id = rp.permission_id
        WHERE ur.user_id = u.id AND ur.revoked_at IS NULL
        AND ur.valid_from <= now() AND (ur.valid_until IS NULL OR ur.valid_until > now())),
      '[]'::json
    ) AS permissions
  FROM sessions s
  JOIN users u ON u.id = s.user_id AND u.is_active
  WHERE s.id = $1 AND s.revoked_at IS NULL AND s.expires_at > now()
`;

interface LoadedSessionRow {
  id: number;
  username: string;
  full_name: string;
  email: string | null;
  must_change_password: boolean;
  is_demo: boolean;
  session_id: string;
  roles: { code: string; nameAr: string; requiresScope: boolean; governorateIds: number[] }[];
  permissions: string[];
}

export async function resolveSession(req: Request, _res: Response, next: NextFunction): Promise<void> {
  try {
    const rawToken = req.cookies?.[config.session.cookieName];
    if (rawToken && typeof rawToken === "string") {
      const row = await one<LoadedSessionRow>(SESSION_LOAD_SQL, [sha256(rawToken)]);
      if (row) {
        req.sessionId = row.session_id;
        req.user = {
          id: row.id,
          username: row.username,
          fullName: row.full_name,
          email: row.email,
          mustChangePassword: row.must_change_password,
          isDemo: row.is_demo,
          roles: row.roles,
          permissions: new Set(row.permissions),
        };
        // Throttled touch so idle sessions still expire from activity
        await one(`UPDATE sessions SET last_seen_at = now() WHERE id = $1 AND last_seen_at < now() - interval '60 seconds'`, [row.session_id]);
      }
    }
    next();
  } catch (error) {
    next(error);
  }
}

export function requireAuth(req: Request, _res: Response, next: NextFunction): void {
  if (!req.user) return next(unauthorized("الجلسة غير صالحة، يرجى تسجيل الدخول"));
  next();
}
