import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { hasErrorLogViewAccessToken } from '../services/error-log-service.js';
import { getSharedResourceTokenDetailByShareToken } from '../services/shared-resource-token-service.js';
import {
  createBaseballTicketBayAlert,
  createBaseballTicketBayLog,
  deleteBaseballTicketBayLog,
  deleteBaseballTicketBayAlert,
  listBaseballTicketBayAlerts,
  listBaseballTicketBayLogs,
  runBaseballTicketBayAlert,
  searchBaseballTicketBayListings,
  updateBaseballTicketBayAlert,
} from '../services/baseball-ticket-bay-service.js';

const timeWindowSchema = z.object({
  id: z.string().trim().min(1),
  start: z.string().trim().regex(/^\d{2}:\d{2}$/),
  end: z.string().trim().regex(/^\d{2}:\d{2}$/),
});

const alertPayloadSchema = z.object({
  title: z.string().trim().min(1).max(255),
  eventDate: z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/),
  team: z.string().trim().min(1).max(50),
  zone: z.string().trim().min(1).max(100),
  aisleSide: z.string().trim().min(1).max(100),
  seatDirections: z.array(z.string().trim().min(1).max(50)).max(10),
  maxPrice: z.number().finite().positive().nullable(),
  seatCount: z.number().int().positive().max(10),
  batchIntervalMinutes: z.number().int().min(1).max(120),
  sameProductAlertEnabled: z.boolean(),
  sameProductNotifyOnce: z.boolean(),
  active: z.boolean().default(true),
  timeWindows: z.array(timeWindowSchema).min(1).max(24),
});

function readHeader(request: { headers: Record<string, string | string[] | undefined> }, key: string) {
  const raw = request.headers[key];
  return Array.isArray(raw) ? String(raw[0] ?? '').trim() : String(raw ?? '').trim();
}

function hasBaseballTicketBayGlobalAccess(request: { headers: Record<string, string | string[] | undefined> }) {
  return hasErrorLogViewAccessToken(request.headers['x-access-token']);
}

type BaseballTicketBayRouteAccessContext =
  | { scope: 'all' }
  | { scope: 'client'; clientId: string }
  | { scope: 'shared-token'; clientId: string; tokenId: string };

function toOwnerScope(accessContext: Exclude<BaseballTicketBayRouteAccessContext, { scope: 'all' }> | { scope: 'all' }) {
  if (accessContext.scope === 'all') {
    return { kind: 'all' } as const;
  }

  if (accessContext.scope === 'shared-token') {
    return { kind: 'owner', ownerType: 'shared-token', ownerId: accessContext.tokenId } as const;
  }

  return { kind: 'owner', ownerType: 'client', ownerId: accessContext.clientId } as const;
}

async function resolveBaseballTicketBayAccessContext(
  request: { headers: Record<string, string | string[] | undefined> },
) : Promise<BaseballTicketBayRouteAccessContext | null> {
  const clientId = readHeader(request, 'x-client-id');

  if (hasBaseballTicketBayGlobalAccess(request)) {
    return { scope: 'all' };
  }

  const accessToken = readHeader(request, 'x-access-token');

  if (accessToken) {
    const sharedTokenDetail = await getSharedResourceTokenDetailByShareToken(accessToken);

    if (
      sharedTokenDetail
      && sharedTokenDetail.token.enabled !== false
      && !sharedTokenDetail.token.revokedAt
      && sharedTokenDetail.token.allowedAppIds.some((item) => item.trim().toLowerCase() === 'baseball-ticket-bay')
    ) {
      if (!clientId) {
        return null;
      }

      return {
        scope: 'shared-token',
        clientId,
        tokenId: sharedTokenDetail.token.id,
      };
    }
  }

  if (!clientId) {
    return null;
  }

  return {
    scope: 'client',
    clientId,
  };
}

export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
  app.post('/api/baseball-ticket-bay/search', async (request) => searchBaseballTicketBayListings(request.body ?? {}));

  app.get('/api/baseball-ticket-bay/alerts', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext) {
      return reply.code(400).send({ message: '접근 식별값이 없어 알림 목록을 불러올 수 없습니다.' });
    }

    return {
      ok: true,
      includeAllClients: accessContext.scope === 'all',
      accessScope: accessContext.scope,
      scopeOwnerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.scope === 'client' ? accessContext.clientId : null,
      items: await listBaseballTicketBayAlerts(toOwnerScope(accessContext)),
    };
  });

  app.get('/api/baseball-ticket-bay/logs', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext) {
      return reply.code(400).send({ message: '접근 식별값이 없어 로그를 불러올 수 없습니다.' });
    }

    const query = z.object({ alertId: z.string().trim().min(1).optional() }).parse(request.query ?? {});

    return {
      ok: true,
      includeAllClients: accessContext.scope === 'all',
      accessScope: accessContext.scope,
      scopeOwnerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.scope === 'client' ? accessContext.clientId : null,
      items: await listBaseballTicketBayLogs(toOwnerScope(accessContext), query.alertId),
    };
  });

  app.delete('/api/baseball-ticket-bay/logs/:id', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext) {
      return reply.code(400).send({ message: '현재 접근 범위에서는 로그를 삭제할 수 없습니다.' });
    }

    const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
    const item = await deleteBaseballTicketBayLog(params.id, toOwnerScope(accessContext));

    if (!item) {
      return reply.code(404).send({ message: '삭제할 로그를 찾지 못했습니다.' });
    }

    return {
      ok: true,
      item,
    };
  });

  app.post('/api/baseball-ticket-bay/alerts', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext || accessContext.scope === 'all') {
      return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 저장할 수 없습니다.' });
    }

    const payload = alertPayloadSchema.parse(request.body ?? {});
    const item = await createBaseballTicketBayAlert(payload, {
      clientId: accessContext.clientId,
      ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
      ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
      appOrigin: readHeader(request, 'x-app-origin'),
      appDomain: readHeader(request, 'x-app-domain'),
    });
    await createBaseballTicketBayLog({
      clientId: accessContext.clientId,
      ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
      ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
      alertId: item.id,
      alertTitle: item.title,
      action: 'create',
      status: 'info',
      message: '알림 조건을 저장했습니다.',
      detail: `${item.team} · ${item.eventDate}`,
    });

    return {
      ok: true,
      item,
    };
  });

  app.patch('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext) {
      return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 수정할 수 없습니다.' });
    }

    const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
    const payload = alertPayloadSchema.partial().parse(request.body ?? {});
    const item = await updateBaseballTicketBayAlert(
      params.id,
      payload,
      accessContext.scope === 'all'
        ? {
            scope: { kind: 'all' },
            appOrigin: readHeader(request, 'x-app-origin'),
            appDomain: readHeader(request, 'x-app-domain'),
          }
        : {
            clientId: accessContext.clientId,
            ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
            ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
            appOrigin: readHeader(request, 'x-app-origin'),
            appDomain: readHeader(request, 'x-app-domain'),
          },
    );
    await createBaseballTicketBayLog({
      clientId: item.clientId,
      ownerType: item.ownerType,
      ownerId: item.ownerId,
      alertId: item.id,
      alertTitle: item.title,
      action: payload.active === false ? 'pause' : payload.active === true ? 'resume' : 'run',
      status: 'info',
      message:
        payload.active === false
          ? '알림을 중지했습니다.'
          : payload.active === true
            ? '알림을 다시 실행 상태로 전환했습니다.'
            : '알림 조건을 수정 저장했습니다.',
      detail: `${item.team} · ${item.eventDate}`,
    });

    return {
      ok: true,
      item,
    };
  });

  app.delete('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext) {
      return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 삭제할 수 없습니다.' });
    }

    const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
    const item = await deleteBaseballTicketBayAlert(params.id, toOwnerScope(accessContext));

    if (!item) {
      return reply.code(404).send({ message: '삭제할 알림을 찾지 못했습니다.' });
    }

    await createBaseballTicketBayLog({
      clientId: item.clientId,
      ownerType: item.ownerType,
      ownerId: item.ownerId,
      alertId: item.id,
      alertTitle: item.title,
      action: 'delete',
      status: 'info',
      message: '알림 항목을 삭제했습니다.',
      detail: `${item.team} · ${item.eventDate}`,
    });

    return {
      ok: true,
      item,
    };
  });

  app.post('/api/baseball-ticket-bay/alerts/:id/run', async (request, reply) => {
    const accessContext = await resolveBaseballTicketBayAccessContext(request);

    if (!accessContext) {
      return reply.code(400).send({ message: '접근 식별값이 없어 즉시 실행할 수 없습니다.' });
    }

    const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});

    const result = await runBaseballTicketBayAlert(params.id, {
      ignoreTimeWindow: true,
      scope: toOwnerScope(accessContext),
    });

    return {
      ok: true,
      alert: result.alert,
      matches: result.matches,
      notifiedMatches: result.notifiedMatches,
      log: result.log,
    };
  });
}
