import type { MiddlewareHandler } from 'hono';
import * as errors from '../custom-errors';
import { IS_DEV_BUILD } from '../build-env';
import { verify } from '../jwt';
import { isSuperAdmin } from '../super-admin';

const { PUBLIC_URL, OFFICER_APP_ORIGIN, MUSIC_APP_ORIGIN } = process.env;

// The allowed production web origin comes from PUBLIC_URL in .env (e.g. https://officer.pastilhas.dev),
// not a hardcoded domain.
const PUBLIC_ORIGIN = (() => {
  try {
    return PUBLIC_URL ? new URL(PUBLIC_URL).origin : undefined;
  } catch {
    return undefined;
  }
})();

const WEB_ORIGINS: string[] = PUBLIC_ORIGIN ? [PUBLIC_ORIGIN] : [];

// Host authorities (`example.com`, or `example.com:8080` off the default port) for the same origins.
// Officer always sits behind an HTTPS reverse proxy, so the proxy's `Host` header is expected to
// match PUBLIC_URL's authority exactly.
const WEB_HOSTS: string[] = WEB_ORIGINS.map((o) => new URL(o).host);

const CHROME_EXTENSIONS: string[] = [
  // 'chrome-extension://<id>'
];

const APP_ORIGINS: string[] = [
  // Officer mobile app — an officer:// custom-scheme origin (with an embedded token), set via env.
  OFFICER_APP_ORIGIN,
  // Standalone officer-music app — its own custom-scheme origin. Allowlisted so it can authenticate
  // and stream; SCOPED_ORIGINS below restricts it to /api/auth + /api/music only.
  MUSIC_APP_ORIGIN,
].filter((o): o is string => Boolean(o));

// The only path prefixes a non-owner account (and the music app) may reach.
const NON_OWNER_PATHS = ['/api/auth', '/api/music'];

function pathAllowed(path: string, prefixes: string[]): boolean {
  return prefixes.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
}

// Per-origin access rules, enforced globally by originScopeMiddleware (mounted in hono.ts):
//  - superAdminOnly: authenticated requests carrying this Origin must be the platform owner
//    (see super-admin.ts). Unauthenticated requests (e.g. signin) pass through — userMiddleware
//    still guards protected routes. Stops a non-owner from using the web / mobile platform, even by
//    replaying a token minted through the music app.
//  - paths: this Origin may reach ONLY these path prefixes; anything else is 403.
// Origins with no rule keep full access. Rules no-op for env values that are unset.
type OriginRule = { superAdminOnly?: boolean; paths?: string[] };
const ORIGIN_RULES: Record<string, OriginRule> = {};
if (PUBLIC_ORIGIN) ORIGIN_RULES[PUBLIC_ORIGIN] = { superAdminOnly: true };
if (OFFICER_APP_ORIGIN) ORIGIN_RULES[OFFICER_APP_ORIGIN] = { superAdminOnly: true };
if (MUSIC_APP_ORIGIN) ORIGIN_RULES[MUSIC_APP_ORIGIN] = { paths: NON_OWNER_PATHS };

// True when an Origin is reserved for the platform owner (used at signin to reject a non-owner login).
export function isSuperAdminOnlyOrigin(origin: string | undefined): boolean {
  return !!origin && ORIGIN_RULES[origin]?.superAdminOnly === true;
}

export function isOriginAllowed(origin: string | undefined, host?: string): boolean {
  if (IS_DEV_BUILD) return true;

  if (origin) {
    if (origin.startsWith('chrome-extension://')) {
      return CHROME_EXTENSIONS.includes(origin);
    }

    if (APP_ORIGINS.includes(origin)) {
      return true;
    }

    return WEB_ORIGINS.includes(origin);
  }

  if (host) {
    return WEB_HOSTS.includes(host);
  }

  return false;
}

export const originValidationMiddleware: MiddlewareHandler = function (ctx, next) {
  const origin = ctx.get('origin') as string | undefined;
  const host = ctx.req.header('host');
  if (!isOriginAllowed(origin, host)) {
    throw errors.FORBIDDEN('Invalid origin');
  }
  return next();
};

function resolveOrigin(headerOrigin: string | undefined, referer: string | undefined): string | undefined {
  if (headerOrigin) return headerOrigin;
  if (referer) {
    try {
      return new URL(referer).origin;
    } catch {
      return undefined;
    }
  }
  return undefined;
}

// Global gate applied to every request (mounted in hono.ts). Reads the Origin header directly (not
// ctx 'origin') so it covers the whole /api tree — the public /api/auth and the protected /api/music
// alike — regardless of which routers mount originMiddleware. Two layers:
//   1. Account backstop (origin-INDEPENDENT): a valid NON-owner token may reach only /api/auth +
//      /api/music, no matter the origin. This is the airtight rule — it holds even if a client omits
//      or forges the Origin header — and is what confines the music accounts to the music app.
//   2. Per-origin rules (ORIGIN_RULES): path scoping (music app) and super-admin-only origins (web +
//      mobile). Redundant with the backstop for the account dimension, but keeps owner-only origins
//      fully off-limits to non-owners (incl. /api/music) and blocks unknown-path access there.
// A missing/invalid token passes both layers (signin needs it; userMiddleware rejects bad tokens on
// protected routes). Only a VALID non-owner token is constrained.
export const originScopeMiddleware: MiddlewareHandler = async function (ctx, next) {
  const path = ctx.req.path;
  const origin = resolveOrigin(ctx.req.header('origin'), ctx.req.header('referer'));

  // Verify the caller's token once (if any); a missing/invalid token stays null.
  const authorization = ctx.req.header('authorization');
  const token = authorization ? authorization.split(' ')[1] : ctx.req.query('token') || undefined;
  let payload: { id?: number } | null = null;
  if (token) {
    try {
      payload = await verify(token);
    } catch {
      payload = null;
    }
  }
  const isOwner = payload ? await isSuperAdmin(payload) : false;

  // 1. Account backstop — non-owner accounts are confined to /api/auth + /api/music everywhere.
  if (payload && !isOwner && !pathAllowed(path, NON_OWNER_PATHS)) {
    throw errors.FORBIDDEN('This account is limited to the music app');
  }

  // 2. Per-origin rules.
  const rule = origin ? ORIGIN_RULES[origin] : undefined;
  if (rule) {
    if (rule.paths && !pathAllowed(path, rule.paths)) {
      throw errors.FORBIDDEN('Origin not permitted for this resource');
    }
    if (rule.superAdminOnly && payload && !isOwner) {
      throw errors.FORBIDDEN('This origin is restricted to the platform owner');
    }
  }

  return next();
};
