import {Hono} from 'hono'
import {serveStatic} from 'hono/bun'
import {cors} from 'hono/cors'
import {HTTPException} from 'hono/http-exception'
import {secureHeaders} from 'hono/secure-headers'

import {auth, requireUser, type AuthVars} from './auth/middleware'
import {env} from './env'
import {joinTrailPage} from './lib/auth-pages'
import {devNetworkGuard} from './lib/dev-network'
import {requestLogger} from './lib/logger'
import {SHARE_TOKEN_REGEX} from './lib/share-links'
import {publicImageUrl} from './lib/source-image'
import authRoutes from './routes/auth'
import blocks from './routes/blocks'
import crumbs from './routes/crumbs'
import dev from './routes/dev'
import dms from './routes/dms'
import extract from './routes/extract'
import feed from './routes/feed'
import friends from './routes/friends'
import places from './routes/places'
import pushTokens from './routes/push-tokens'
import recommendations from './routes/recommendations'
import reports from './routes/reports'
import search from './routes/search'
import trails from './routes/trails'
import users from './routes/users'
import {findActiveShareLink} from './services/trails'

const guarded = new Hono<{Variables: AuthVars}>()
  // e2e offline simulation: when the dev network toggle is down, fail every
  // authed request before it reaches a handler — the mobile rpc translates
  // it into a synthetic network error so the outbox keeps the write queued.
  // inert in production: the /dev route that flips the flag 404s there
  .use('*', devNetworkGuard)
  .use('*', auth, requireUser)
  .route('/blocks', blocks)
  .route('/crumbs', crumbs)
  .route('/dms', dms)
  .route('/extract', extract)
  .route('/feed', feed)
  .route('/friends', friends)
  .route('/places', places)
  .route('/push-tokens', pushTokens)
  .route('/recommendations', recommendations)
  .route('/reports', reports)
  .route('/search', search)
  .route('/trails', trails)
  .route('/users', users)

const app = new Hono()
  .use('*', requestLogger())
  .use('*', secureHeaders())
  .use(
    '*',
    cors({
      origin: (origin) => {
        if (!origin) return undefined
        try {
          const {protocol, hostname} = new URL(origin)
          if (
            protocol === 'http:' &&
            (hostname === 'localhost' || hostname === '127.0.0.1')
          )
            return origin
          if (
            hostname === 'exp.direct' ||
            hostname.endsWith('.exp.direct')
          )
            return origin
        } catch {}
        return undefined
      },
    }),
  )
  .onError((err, c) => {
    if (err instanceof HTTPException) return err.getResponse()
    console.error(err)
    return c.json({error: 'internal_error'}, 500)
  })
  .get('/health', (c) => c.json({ok: true}))
  // serves processed avatars under the disk driver; under r2 nothing
  // is written here so requests simply 404
  .use(
    '/static/*',
    serveStatic({
      root: env.UPLOADS_DIR,
      rewriteRequestPath: (path) => path.replace(/^\/static/, ''),
      onFound: (_path, c) => {
        c.header(
          'Cache-Control',
          'public, max-age=31536000, immutable',
        )
      },
    }),
  )
  // a missing public asset is a 404, not the authed catch-all's 401
  .get('/static/*', (c) => c.text('not found', 404))
  // public landing for shared join links; the join itself requires
  // auth. the preview only renders for a live token — possession of
  // the token already grants join, so it leaks nothing beyond that
  .get('/trails/join/:token', async (c) => {
    const token = c.req.param('token')
    if (!SHARE_TOKEN_REGEX.test(token))
      return c.text('not found', 404)
    const link = await findActiveShareLink(token)
    return c.html(
      joinTrailPage(
        `${env.APP_SCHEME}://trail/join/${token}`,
        link
          ? {
              name: link.trail.name,
              description: link.trail.description,
              coverImageUrl: publicImageUrl(
                link.trail.coverImageRef ?? link.coverFallbackRef,
              ),
            }
          : undefined,
      ),
    )
  })
  .route('/auth', authRoutes)
  // test-only; 404s unless DEV_ROUTES=true, and always in production
  .route('/dev', dev)
  .route('/', guarded)

export type AppType = typeof app
export default app
