import express from 'express';
import { mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineApp, page, type LivePageRequestContext } from 'redweb';
import { DashboardAuth, sessionToken } from './shared/auth.js';
import { DashboardStore } from './shared/store.js';
import { matchRoute } from './routes.js';

export const databasePath = () => resolve(process.env.GAME_DATABASE ?? 'data/game.sqlite');
export interface Options { port?: number; database?: string; origin?: string; signals?: boolean; }

export function gameApp(options: Options = {}) {
    let store: DashboardStore;
    let auth: DashboardAuth;
    const configured = options.origin ?? process.env.GAME_ORIGIN;
    if (configured && (!/^https?:$/.test(new URL(configured).protocol) || new URL(configured).origin !== configured)) throw new Error('GAME_ORIGIN must be an exact HTTP(S) origin.');
    if (process.env.NODE_ENV === 'production' && !configured?.startsWith('https://')) throw new Error('Set an HTTPS GAME_ORIGIN for production.');
    const origin = () => configured ?? `http://localhost:${(app.server!.address() as { port: number }).port}`;
    // Exact browser origin: do not derive trust from forwarded headers.
    const origins = (candidate: string | undefined) => candidate === origin();
    const account = (request: { headers: { cookie?: string | readonly string[] } }) =>
        store.session(sessionToken(typeof request.headers.cookie === 'string' ? request.headers.cookie : undefined))?.account;
    const MatchRoute = matchRoute(account, origins);

    @page('/login', { live: false })
    class Login {
        render() { return <main><h1>Sign in to play</h1><p>Use an account created with npm run add-user.</p>
            <form method="post" action="/login"><label>Account <input name="account" autocomplete="username" required /></label>
                <label>Password <input name="password" type="password" autocomplete="current-password" required /></label>
                <button>Sign in</button></form></main>; }
    }
    const Board = () => <div id="board" aria-label="Game board">{Array.from({ length: 9 }, (_, cell) =>
        <button data-cell={cell} aria-label={`Square ${cell + 1}`} disabled>·</button>)}</div>;

    @page('/', { live: false, authorize: context => Boolean(account(context.request)) })
    class GamePage {
        render(context: LivePageRequestContext) { return <main><h1>Tic-tac-toe</h1><p>Signed in as {context.principal}</p>
            <form method="post" action="/logout"><button>Sign out all sessions</button></form>
            <p>Share a room name with another player. The first two accounts reserve its seats.</p>
            <form id="join"><label>Room <input name="room" pattern="[a-z0-9-]{1,32}" maxlength="32" required /></label><button>Join room</button></form>
            <p id="connection" role="status">Connecting…</p><p id="status" role="status">Choose a room.</p>
            <Board /><p id="notice" role="alert"></p><p><a href="/">Choose another room</a></p>
            <link rel="stylesheet" href="/game.css" /><script type="module" src="/client.js"></script>
        </main>; }
    }
    class Accounts {
        onInit() {
            const filename = options.database ?? databasePath();
            mkdirSync(dirname(filename), { recursive: true });
            store = new DashboardStore(filename);
            auth = new DashboardAuth(store);
            app.app!.use(express.urlencoded({ extended: false, limit: '4kb', parameterLimit: 4 }));
            app.app!.get('/client.js', (_request, response) => response.sendFile(fileURLToPath(new URL('./client.js', import.meta.url))));
            app.app!.get('/game.css', (_request, response) => response.sendFile(fileURLToPath(new URL('../src/game.css', import.meta.url))));
            auth.mount(app.app!, origin, origins, async user => {
                const route = app.sockets!.routes.find(route => route.path === '/match')!;
                for (const socket of route.clients.values()) if (socket.context?.principal === user) socket.close(1008, 'Signed out.');
            });
        }
        onShutdown() { auth?.close(); store?.close(); }
    }
    const app = defineApp({ pages: [Login, GamePage], sockets: [MatchRoute], services: [Accounts],
        port: options.port ?? Number(process.env.PORT ?? 8181), bind: '127.0.0.1', signals: options.signals, logger: null,
        authenticate: request => request.url?.split('?')[0] === '/login' ? true : account(request),
        origins,
    });
    return app;
}
