/**
 * Survey REST routes — admin CRUD + public response endpoints
 * @module surveys/routes
 */

import type { FastifyInstance } from 'fastify';
import {
  UnauthorizedError,
  ForbiddenError,
  NotFoundError,
  BadRequestError,
} from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import { evaluateTarget } from '../broadcasts/targeting.js';
import * as subsRepo from '../subscriptions/repository.js';
import * as repo from './repository.js';
import {
  CreateSurveySchema,
  UpdateSurveySchema,
  SubmitAnswerSchema,
  SurveyStatus,
  computeCompletionRate,
  validateAnswerType,
  type Survey,
  type SurveyResponse,
} from './types.js';

// =============================================================================
// Auth Helpers
// =============================================================================

function requireAuth(req: { jwtPayload?: { sub: string } }): string {
  if (!req.jwtPayload?.sub) throw new UnauthorizedError('Authentication required');
  return req.jwtPayload.sub;
}

function requireAdmin(req: { jwtPayload?: { sub: string; role?: string } }): string {
  const userId = requireAuth(req);
  if (req.jwtPayload?.role !== 'admin') {
    throw new ForbiddenError('Admin access required');
  }
  return userId;
}

// =============================================================================
// Admin Routes
// =============================================================================

async function adminRoutes(app: FastifyInstance): Promise<void> {
  // List all surveys
  app.get('/', async req => {
    const adminId = requireAdmin(req);
    const productId = getRequestProductId(req);

    const { status } = req.query as { status?: string };
    const { surveys, total } = await repo.listSurveys(productId, {
      status: status as Survey['status'],
    });

    // Add computed completion rate
    const surveysWithRate = surveys.map(s => ({
      ...s,
      computedCompletionRate: computeCompletionRate(s.metrics),
    }));

    req.log.info({ adminId, productId, count: surveys.length }, 'Listed surveys');
    return { surveys: surveysWithRate, total };
  });

  // Get single survey with questions
  app.get<{ Params: { id: string } }>('/:id', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    return survey;
  });

  // Create survey
  app.post('/', async (req, reply) => {
    const adminId = requireAdmin(req);
    const productId = getRequestProductId(req);

    const input = CreateSurveySchema.parse(req.body);

    const now = new Date().toISOString();
    const survey: Survey = {
      id: `survey_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      productId,
      ...input,
      status: SurveyStatus.DRAFT,
      target: { platforms: ['web', 'ios', 'android'] }, // Default target
      metrics: {
        impressions: 0,
        starts: 0,
        completions: 0,
        avgTimeSeconds: 0,
        incentiveClaims: 0,
      },
      createdAt: now,
      updatedAt: now,
      createdBy: adminId,
    };

    const created = await repo.createSurvey(survey);
    req.log.info({ surveyId: created.id, adminId }, 'Created survey');

    reply.status(201);
    return created;
  });

  // Update survey
  app.put<{ Params: { id: string } }>('/:id', async req => {
    const adminId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const existing = await repo.getSurvey(id, productId);
    if (!existing) throw new NotFoundError('Survey not found');

    // Cannot edit closed surveys
    if (existing.status === SurveyStatus.CLOSED) {
      throw new BadRequestError('Cannot edit closed survey');
    }

    const updates = UpdateSurveySchema.parse(req.body);

    const updated = await repo.updateSurvey(id, productId, updates);
    req.log.info({ surveyId: id, adminId }, 'Updated survey');

    return updated;
  });

  // Delete survey
  app.delete<{ Params: { id: string } }>('/:id', async (req, reply) => {
    const adminId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const deleted = await repo.deleteSurvey(id, productId);
    if (!deleted) throw new NotFoundError('Survey not found');

    req.log.info({ surveyId: id, adminId }, 'Deleted survey');
    reply.status(204);
    return;
  });

  // Duplicate survey
  app.post<{ Params: { id: string } }>('/:id/duplicate', async req => {
    const adminId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const existing = await repo.getSurvey(id, productId);
    if (!existing) throw new NotFoundError('Survey not found');

    const now = new Date().toISOString();
    const cloned: Survey = {
      ...existing,
      id: `survey_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      title: `${existing.title} (Copy)`,
      status: SurveyStatus.DRAFT,
      metrics: {
        impressions: 0,
        starts: 0,
        completions: 0,
        avgTimeSeconds: 0,
        incentiveClaims: 0,
      },
      createdAt: now,
      updatedAt: now,
      createdBy: adminId,
    };

    const created = await repo.createSurvey(cloned);
    req.log.info({ surveyId: created.id, parentId: id, adminId }, 'Cloned survey');

    return created;
  });

  // Pause survey
  app.post<{ Params: { id: string } }>('/:id/pause', async req => {
    const adminId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const existing = await repo.getSurvey(id, productId);
    if (!existing) throw new NotFoundError('Survey not found');

    if (existing.status !== SurveyStatus.ACTIVE) {
      throw new BadRequestError('Can only pause active surveys');
    }

    await repo.updateSurvey(id, productId, { status: SurveyStatus.PAUSED });
    req.log.info({ surveyId: id, adminId }, 'Paused survey');

    return { success: true };
  });

  // List responses
  app.get<{ Params: { id: string } }>('/:id/responses', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const {
      isComplete,
      limit: limitStr,
      offset: offsetStr,
    } = req.query as {
      isComplete?: string;
      limit?: string;
      offset?: string;
    };

    const parsedLimit = limitStr ? parseInt(limitStr, 10) : 50;
    const parsedOffset = offsetStr ? parseInt(offsetStr, 10) : 0;
    const safeLimit =
      Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 200) : 50;
    const safeOffset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    const { responses, total } = await repo.listResponsesForSurvey(id, {
      isComplete: isComplete === 'true' ? true : isComplete === 'false' ? false : undefined,
      limit: safeLimit,
      offset: safeOffset,
    });

    return { responses, total };
  });

  // List respondents (just user IDs)
  app.get<{ Params: { id: string } }>('/:id/respondents', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    const userIds = await repo.listRespondents(id);
    return { userIds, count: userIds.length };
  });

  // Get analytics
  app.get<{ Params: { id: string } }>('/:id/analytics', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    const analytics = await repo.getSurveyAnalytics(id, productId);

    return {
      surveyId: id,
      ...analytics,
      computedMetrics: {
        completionRate: computeCompletionRate({
          starts: analytics.totalStarts,
          completions: analytics.totalCompletions,
          impressions: 0,
          avgTimeSeconds: analytics.avgTimeSeconds,
          incentiveClaims: 0,
        }),
      },
    };
  });

  // Export CSV
  app.get<{ Params: { id: string } }>('/:id/export.csv', async (req, reply) => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    const { responses } = await repo.listResponsesForSurvey(id, { isComplete: true });

    const csv = repo.exportResponsesToCSV(responses);

    reply.header('Content-Type', 'text/csv');
    reply.header('Content-Disposition', `attachment; filename="${survey.id}_responses.csv"`);
    return csv;
  });
}

// =============================================================================
// Public/User Routes
// =============================================================================

async function publicRoutes(app: FastifyInstance): Promise<void> {
  // Get active survey for user (rate limited in server.ts)
  app.get('/active', async req => {
    const userId = requireAuth(req);
    const productId = getRequestProductId(req);

    // Get request headers for targeting context
    const headers: Record<string, string | undefined> = {
      'x-platform': req.headers['x-platform'] as string | undefined,
      'x-app-version': req.headers['x-app-version'] as string | undefined,
      'x-os-version': req.headers['x-os-version'] as string | undefined,
      'x-country-code': req.headers['x-country-code'] as string | undefined,
      'x-region-code': req.headers['x-region-code'] as string | undefined,
    };

    // Get user segments from JWT or default to ['free']
    const segments = (req.jwtPayload as { segments?: string[] } | undefined)?.segments ?? ['free'];

    // Build targeting context
    const context: import('../broadcasts/types.js').TargetingContext = {
      userId,
      productId,
      platform: (headers['x-platform'] ?? 'web') as import('../broadcasts/types.js').Platform,
      appVersion: headers['x-app-version'] ?? '0.0.0',
      osVersion: headers['x-os-version'] ?? '0.0.0',
      countryCode: headers['x-country-code'],
      regionCode: headers['x-region-code'],
      userSegments: segments as string[] as import('../broadcasts/types.js').UserSegment[],
    };

    // Find all active surveys
    const { surveys } = await repo.listSurveys(productId, { status: SurveyStatus.ACTIVE });

    // Find first survey user qualifies for and hasn't completed/dismissed
    for (const survey of surveys) {
      // Check if user already has state for this survey
      const state = await repo.getUserSurveyState(survey.id, userId);

      // Skip if already completed or dismissed
      if (state?.status === 'completed' || state?.status === 'dismissed') {
        continue;
      }

      // Check targeting
      if (evaluateTarget(survey.target, context)) {
        // Update impression metrics
        await repo.updateSurveyMetrics(survey.id, productId, {
          impressions: survey.metrics.impressions + 1,
        });

        // Track that this user was shown the survey
        await repo.incrementSurveyShowCount(survey.id, userId, productId);

        return {
          survey: {
            id: survey.id,
            title: survey.title,
            description: survey.description,
            questions: survey.questions,
            incentive: survey.incentive,
            displayTrigger: survey.displayTrigger,
          },
        };
      }
    }

    // No eligible survey found
    return { survey: null };
  });

  // Start survey session
  app.post<{ Params: { id: string } }>('/:id/start', async req => {
    const userId = requireAuth(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');
    if (survey.status !== SurveyStatus.ACTIVE) {
      throw new BadRequestError('Survey is not active');
    }

    const now = new Date().toISOString();

    // Check for existing response
    const existing = await repo.getResponse(id, userId);
    if (existing) {
      // Return existing session
      return {
        responseId: existing.id,
        startedAt: existing.startedAt,
        currentQuestionIndex: existing.currentQuestionIndex,
        answers: existing.answers,
      };
    }

    // Create new response
    const response: SurveyResponse = {
      id: `${id}:${userId}`,
      surveyId: id,
      productId,
      userId,
      deviceId: req.headers['x-device-id'] as string | undefined,
      answers: {},
      currentQuestionIndex: 0,
      startedAt: now,
      isComplete: false,
      incentiveClaimed: false,
      createdAt: now,
      updatedAt: now,
    };

    await repo.createResponse(response);

    // Update survey metrics
    await repo.updateSurveyMetrics(id, productId, {
      starts: survey.metrics.starts + 1,
    });

    // Update user state
    await repo.upsertUserSurveyState({
      id: `${id}:${userId}`,
      surveyId: id,
      userId,
      productId,
      status: 'started',
      showCount: 1,
      startedAt: now,
    });

    return {
      responseId: response.id,
      startedAt: now,
      currentQuestionIndex: 0,
      answers: {},
    };
  });

  // Submit answer
  app.post<{ Params: { id: string } }>('/:id/response', async req => {
    const userId = requireAuth(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const { questionId, answer } = SubmitAnswerSchema.parse(req.body);

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    // Find the question
    const question = survey.questions.find(q => q.id === questionId);
    if (!question) throw new NotFoundError('Question not found');

    // Validate answer type matches question type
    const validation = validateAnswerType(question, answer);
    if (!validation.valid) {
      throw new BadRequestError(validation.error ?? 'Invalid answer type');
    }

    // Get or create response
    let response = await repo.getResponse(id, userId);
    if (!response) {
      throw new BadRequestError('Survey session not started');
    }

    // Update answer
    const updatedAnswers = { ...response.answers, [questionId]: answer };

    // Find next question index (skipping conditional questions)
    const nextIndex = response.currentQuestionIndex + 1;

    response = await repo.updateResponse(id, userId, {
      answers: updatedAnswers,
      currentQuestionIndex: nextIndex,
    });

    if (!response) throw new Error('Failed to update response');

    return {
      responseId: response.id,
      currentQuestionIndex: response.currentQuestionIndex,
      answers: response.answers,
    };
  });

  // Complete survey
  app.post<{ Params: { id: string } }>('/:id/complete', async req => {
    const userId = requireAuth(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const survey = await repo.getSurvey(id, productId);
    if (!survey) throw new NotFoundError('Survey not found');

    const response = await repo.getResponse(id, userId);
    if (!response) throw new NotFoundError('Survey session not found');

    const now = new Date().toISOString();

    // Check for required questions
    const unansweredRequired = survey.questions.filter(q => q.required && !response.answers[q.id]);
    if (unansweredRequired.length > 0) {
      throw new BadRequestError(
        `Required questions unanswered: ${unansweredRequired.map(q => q.id).join(', ')}`
      );
    }

    // Calculate time spent
    const timeSpentSeconds = Math.floor(
      (new Date(now).getTime() - new Date(response.startedAt).getTime()) / 1000
    );

    // Update response as complete
    const updated = await repo.updateResponse(id, userId, {
      isComplete: true,
      completedAt: now,
    });

    if (!updated) throw new Error('Failed to complete survey');

    // Update survey metrics
    const newAvgTime =
      survey.metrics.completions > 0
        ? (survey.metrics.avgTimeSeconds * survey.metrics.completions + timeSpentSeconds) /
          (survey.metrics.completions + 1)
        : timeSpentSeconds;

    await repo.updateSurveyMetrics(id, productId, {
      completions: survey.metrics.completions + 1,
      avgTimeSeconds: newAvgTime,
    });

    // Update user state
    await repo.upsertUserSurveyState({
      id: `${id}:${userId}`,
      surveyId: id,
      userId,
      productId,
      status: 'completed',
      showCount: 1,
      completedAt: now,
    });

    // Handle incentive fulfillment
    let incentiveClaimed = false;
    if (survey.incentive) {
      const sub = await subsRepo.getByUserId(userId, productId);
      if (sub) {
        if (survey.incentive.type === 'pro_days') {
          const currentEnd = new Date(sub.currentPeriodEnd);
          currentEnd.setDate(currentEnd.getDate() + survey.incentive.amount);
          await subsRepo.updateSubscription(sub.id, userId, {
            currentPeriodEnd: currentEnd.toISOString(),
          });
        } else if (survey.incentive.type === 'credits') {
          await subsRepo.updateSubscription(sub.id, userId, {
            tokensIncluded: sub.tokensIncluded + survey.incentive.amount,
          });
        }
        incentiveClaimed = true;
        await repo.updateResponse(id, userId, {
          incentiveClaimed: true,
          incentiveClaimedAt: now,
        });

        await repo.updateSurveyMetrics(id, productId, {
          incentiveClaims: survey.metrics.incentiveClaims + 1,
        });
      }
    }

    return {
      success: true,
      timeSpentSeconds,
      incentiveClaimed,
    };
  });

  // Dismiss survey
  app.post<{ Params: { id: string } }>('/:id/dismiss', async req => {
    const userId = requireAuth(req);
    const productId = getRequestProductId(req);
    const { id } = req.params;

    const now = new Date().toISOString();

    await repo.upsertUserSurveyState({
      id: `${id}:${userId}`,
      surveyId: id,
      userId,
      productId,
      status: 'dismissed',
      showCount: 1,
      dismissedAt: now,
    });

    return { success: true };
  });
}

// =============================================================================
// Main Route Registration
// =============================================================================

export async function surveyRoutes(app: FastifyInstance): Promise<void> {
  // Admin routes
  await app.register(adminRoutes, { prefix: '/admin/surveys' });

  // Public routes
  await app.register(publicRoutes, { prefix: '/surveys' });
}
