import type { HubSpotObjectType } from '../integrations/hubspot/contracts';
import { HUBSPOT_OBJECT_TYPES } from '../integrations/hubspot/contracts';
import { HubSpotOAuthError } from '../integrations/hubspot/oauth';
import { Hono } from 'hono';
import { z } from 'zod';
import { requireAnyTeamCapability, requireCapability } from '../lib/auth';
import type { ApiEnv } from '../lib/mutation';

const connectionParamSchema = z.string().uuid();

export interface HubSpotConnectionSummary {
  id: string;
  portalId: string;
  displayName: string | null;
  status: string;
  grantedScopes: readonly string[];
  accessTokenExpiresAt: Date;
  installedAt: Date;
  lastSyncAt: Date | null;
  lastError: string | null;
}

export interface HubSpotRouteService {
  begin(requestedByUserId: string): Promise<{ authorizationUrl: string }>;
  complete(code: string, state: string, signal?: AbortSignal): Promise<{ returnPath: string }>;
  listConnections(): Promise<readonly HubSpotConnectionSummary[]>;
  enqueueSync(connectionId: string, objectTypes: readonly HubSpotObjectType[], requestedByUserId: string): Promise<{ jobIds: string[] }>;
}

export function createHubSpotRoutes(service: HubSpotRouteService): Hono<ApiEnv> {
  const routes = new Hono<ApiEnv>();

  routes.post('/api/integrations/hubspot/oauth/start', async (context) => {
    const principal = context.get('principal');
    requireCapability(principal, 'settings:admin');
    return context.json(await service.begin(principal.userId));
  });

  routes.get('/api/integrations/hubspot/oauth/callback', async (context) => {
    const code = context.req.query('code');
    const state = context.req.query('state');
    if (!code || !state) {
      return context.json({ error: 'HubSpot did not return an authorization code and state.' }, 400);
    }
    try {
      const completed = await service.complete(code, state, context.req.raw.signal);
      return context.redirect(completed.returnPath, 303);
    } catch (error) {
      if (error instanceof HubSpotOAuthError) {
        return context.json({ error: error.message }, 400);
      }
      throw error;
    }
  });

  routes.get('/api/integrations/hubspot/connections', async (context) => {
    if (!context.get('principal').scopes.includes('read')) {
      return context.json({ error: "This credential lacks the 'read' scope." }, 403);
    }
    const connections = await service.listConnections();
    return context.json({
      connections: connections.map((connection) => ({
        ...connection,
        accessTokenExpiresAt: connection.accessTokenExpiresAt.toISOString(),
        installedAt: connection.installedAt.toISOString(),
        lastSyncAt: connection.lastSyncAt?.toISOString() ?? null,
      })),
    });
  });

  routes.post('/api/integrations/hubspot/connections/:connectionId/sync', async (context) => {
    const principal = context.get('principal');
    // Any team, and honestly so: a HubSpot sync pulls companies, contacts and
    // deals from both sides at once, so there is no single team to scope it to.
    // Narrowing it would need the sync to accept an object-type filter first.
    requireAnyTeamCapability(principal, 'data:import');
    const parsedId = connectionParamSchema.safeParse(context.req.param('connectionId'));
    if (!parsedId.success) return context.json({ error: 'Invalid HubSpot connection ID.' }, 400);
    return context.json(
      await service.enqueueSync(parsedId.data, HUBSPOT_OBJECT_TYPES, principal.userId),
      202,
    );
  });

  return routes;
}
